Kernel programming is thoroughly explained in AIX Version 4.3 Kernel Extensions and Device Support Programming Concepts. This section only highlights the major changes required for multiprocessor systems.
Serialization is often required when accessing certain critical resources. Locking services can be used to serialize thread access in the process environment, but they will not protect against an access occurring in the interrupt environment. Previously, a kernel service disabled interrupts using the i_disable kernel service to serialize interrupt level access. However, this strategy does not work in a multiprocessor environment. Therefore, new or ported code should use the disable_lock and unlock_enable kernel services, which use simple locks in addition to interrupt control. These kernel services can also be used for uniprocessor systems, on which they simply use interrupt services without locking. See Locking Kernel Services in AIX Version 4.3 Kernel Extensions and Device Support Programming Concepts for detailed information.
Device drivers by default run in a logical uniprocessor environment, in what is called funnelled mode. Most well-written drivers for AIX uniprocessor systems will work without modification in this mode, but must be carefully examined and modified to benefit from multiprocessing. Finally, kernel services for timers now have return values because they will not always succeed in a multiprocessor environment. Therefore, new or ported code must check these return values. See Using Multiprocessor-Safe Timer Services in AIX Version 4.3 Kernel Extensions and Device Support Programming Concepts for more information.
In AIX, applications are either 32-bit applications or 64-bit applications.
A 32-bit application is an application that executes in an environment where addresses are 32 bits long and can represent 4 gigabytes of addressability (virtual address space).
A 64-bit application is an application that executes in an environment where addresses are 64 bits long and can represent much larger addressability (over a billion gigabytes).
When creating an application, a decision must be made whether to create a 32-bit application or a 64-bit application. 32-bit applications can be run on any Risc System/6000 system. 64-bit applications can only be run on 64-bit Risc System/6000 systems. Both 32-bit and 64-bit applications (and libraries) can be compiled and linked on both 32-bit and 64-bit systems.
If the same source code is used to create both a 32-bit and a 64-bit application, the 64-bit application will be the same size or larger than the 32-bit application and will generally run no faster (and often slower) than the 32-bit application, unless it makes use of the larger 64-bit addressability to improve its performance. Thus, the correct choice should generally be to create a 32-bit application unless 64-bit addressability is required by the application or can be used to dramatically improve its performance. For this reason, the default mode for development tools is to create 32-bit objects and applications.
The 64-bit address space can be used to dramatically improve the performance of applications that manipulate large amounts of data. This data can either be created within the application or obtained from files. Generally the performance gain comes from the fact that the 64-bit application can contain the data in its address space (either created in data structures or mapped into memory), where the data would not fit into a 32-bit address space. Thus the data would need to be multiple gigabytes in size or larger to show this benefit.
There are two reasons for this performance improvement. First, the system call overhead of reading and writing files can be avoided by mapping the files into memory. Second, 64-bit systems can support physical memories that are larger than the addressability of 32-bit applications, so 64-bit applications are needed to make full use of the physical memory available.
64-bit libraries and applications can only be created from 64-bit objects. A 64-bit object is an object type (64-bit XCOFF format) created by compilation or assembly in 64-bit mode. (This does not mean that the compiler or assembler executes in a 64-bit execution environment, just that the compiler or assembler has been requested to create 64-bit objects rather than 32-bit objects.) 32-bit XCOFF format was the only object type in releases of AIX before release 4.3.
There is no way to create an object or application using both 32-bit and 64-bit object files. A system provided library contains both 32-bit and 64-bit object files. The linker selects the appropriate objects from the library based on the type of linking that is requested (32-bit or 64-bit) and creates an object or application of that type.
There are two archive file types in AIX. The first does not recognize 64-bit object files and cannot be larger than 2 gigabytes. This was the only archive file type in releases of AIX before release 4.3. The second archive file type recognizes both 32-bit and 64-bit object files and will work with files larger than 2 gigabytes.
In addition to the differences in addressability, there are the following differences between the 32-bit and 64-bit execution environments (or modes):
All C fundamental types other than "long" and pointer types will be the same size in 32-bit and 64-bit compilation modes.
AIX provides support in all the standard tools for building, examining, and debugging 64-bit applications.
yacc, lex, and lint work with source code destined for both 32-bit and 64-bit compilation.
The C and Fortran compilers and the assembler allow the creation of both 32-bit and 64-bit objects. The linker allows the creation of both 32-bit and 64-bit objects and applications.
make, ar, strip, dump, nm, prof, gprof, lorder, ranlib, size, strings, and sum work with both 32-bit and 64-bit objects and applications.
dbx and xldb allow the debugging of both 32-bit and 64-bit applications.
The following issues must be examined and dealt with when porting source code for 32-bit applications to be compiled in 64-bit mode to create 64-bit applications:
The -t flag to lint can be used to find issues when porting source code from 32-bit to 64-bit compilation mode.
The APIs (Application Programming Interfaces) provided to 32-bit applications are also generally provided to 64-bit applications. Some libraries that have been superseded or deprecated for 32-bit applications are not being provided to 64-bit applications, so their APIs will be missing in 64-bit execution mode.
The libraries an application links to will be the same whether the application is built for 32-bit or 64-bit execution mode. This is because system libraries provide object files for both 32-bit and 64-bit application use in the same library archive file.
The names of types, global variables, preprocessor macros, and predefined constants are the same in 32-bit and 64-bit compilation mode. The sizes (and layouts in the case of structures) and values for these are often different in 32-bit and 64-bit compilation mode, to account for the different sizes of the address spaces and fundamental types involved.
The names of API functions, the types of parameters passed, and the return types are the same in 32-bit and 64-bit compilation modes. The sizes (and layouts in the case of structures) of the parameters and return values are often different in 32-bit and 64-bit compilation modes to account for the different size of the address spaces involved.
Libraries should provide the same functionality to 64-bit applications that they provide to 32-bit applications. This is to minimize the amount of porting that needs to be done when changing the execution mode of an application from 32-bit to 64-bit. To ease porting, the names of functions provided, their parameter types and return types should be the same for 32-bit and 64-bit applications. This will allow a well-written application to be built in both 32-bit to 64-bit versions with no changes.
The choice of the types of integral parameters and return values should be made based upon what a parameter or return value is representing. If it represents the size of an object in the address space, its type should be based upon a C "long" type. Otherwise, the type should be made one of the C types "char", "short", "int", or "long long", depending on what the maximum possible value is. (These types are the same size in 32-bit and 64-bit compilation mode.)
Libraries should contain both the 32-bit and 64-bit objects files for the API they support. This will minimize the porting effort for makefiles for applications that are being ported from 32-bit to 64-bit execution mode.
Only 32-bit objects can be loaded by a 32-bit application. Only 64-bit objects can be loaded by 64-bit applications. If an API is provided by loading objects, a separate 32-bit and 64-bit version of the object must be provided with a different pathname.
For these old kernel extensions and all kernel extensions which have not been designed to work with 64-bit applications, only 32-bit applications can be supported. A 64-bit application will fail to link if it attempts to make use of a system call from a kernel extension that has not been modified to support 64-bit applications. A kernel extension can indicate that it supports 64-bit applications by setting the SYS_64BIT flag when it is loaded using the sysconfig routine.
Kernel extension support for 64-bit applications has two aspects.
The first aspect is the use of new kernel services for working with the 64-bit user address space. The new 64-bit services for examining and manipulating the 64-bit address space are as_att64, as_det64, as_geth64, as_puth64, as_seth64, and as_getsrval64. The new services for copying data to or from 64-bit address spaces are copyin64, copyout64, copyinstr64, fubyte64, fuword64, subyte64, and suword64. The new service for doing cross-memory attaches to memory in a 64-bit address space is xmattach64. The new services for creating real memory mappings are rmmap_create64 and rmmap_remove64. The major difference between all these services and their 32-bit counterparts is that they use 64 bit user addresses rather than 32 bit user addresses.
The new service for determining whether a process (and its address space) is 32-bit or 64-bit is IS64U.
The second aspect of supporting 64-bit applications is taking 64 bit user data passed to system calls and transforming that data to 32 bit data which can be passed through the system call handler to the kernel extension. If the types of the parameters passed to a system call are all 32 bit sized or smaller (in 64-bit compilation mode), then no extra work is required. However, if 64-bit data (long or long long C types) or addresses are passed to the system call, then the data must be split and passed in two parameters or transformed into a 32-bit value.
To assist in using 64-bit user addresses in kernel mode, a set of "remapping" services are provided which transform the 64 bit user addresses into 32 bit addresses which can be used by most of the old 32-bit user address manipulation services in the kernel. In this way, kernel code which has not been modified to work with 64-bit user addresses an always use the 32 bit address (either from a 32-bit application or as a transformed 64-bit address from a 64-bit application). Services such as copyin, copyout, xmattach, fuword, fubyte, suword, and subyte will correctly work with the 32-bit address by transforming it back to a 64-bit address.
The remapping services consist of a set of routines which can be called (in 64-bit execution mode) from a library and a matching set of routines which can be called (in 32-bit mode) from a kernel extension. The library routines determine which 32-bit addresses should be matched to each 64-bit address and package this information in a data structure to be passed to the kernel. The library then passes all the parameters (including the 32-bit addresses which were associated with the 64-bit addresses) through the 64-bit system call to the kernel extension.
The kernel extension passes the remapping information (created by the library remapping routine) to the remapping kernel service, which saves it for use by address space services for the duration of that system call.
A limited number of addresses can be remapped because remapping must be done on a segment (256K bytes) basis. One address remapping can use multiple of these segments, depending on the location of the address relative to a segment boundary and depending on the length of the address range being remapped. Multiple address remappings can utilize a single segment of remapping if they all happen to fall in the range of a single segment.
To simplify the use of these remapping services, a set of macros have been declared (in sys/remap.h) which hide many of the underlying details. These macros should be used to avoid mistakes in the use of the underlying services. REMAP_DCL is used to declared the data structures used by the other services. REMAP_SETUP and REMAP_SETUP_WITH_LEN are used to fill in the data structures with the 64-bit addresses and lengths. REMAP, REMAP_VOID, REMAP_IDENTITY, and REMAP_IDENTITY_VOID are called by the library to determine the remappings.
Programming on Multiprocessor Systems: Overview