Wiley.Symbian.OS.Internals.Real.time.Kernel.Programming.Dec.2005.eBook-DDU (779891), страница 84
Текст из файла (страница 84)
Again, once theDLL is loaded, the loader goes on to resolve its import dependencies inthe same way as I described previously.10.3.3 The loader cacheFrom the previous section, you can see that the loading of an executablethat has dependencies can involve the loader searching many directorieson multiple drives. Furthermore, to determine if a candidate executablefully matches the criteria required by the client or importing executable,then the loader must read header information from those files that matchthe required filename.To read directory entries and file headers, the loader server needsto makes requests on the file server and so it permanently has a fileserver session open. But to optimize the speed at which executables areloaded, and to reduce the number of requests made to the file server, weimplemented a loader cache.For a small number of directories, the loader caches the names ofevery file that they contain.
It stores only the files’ ‘‘rootnames’’ in ASCII.The ‘‘rootname’’ is the basic name and extension with any version orUID information removed. We use ASCII since names of executablesdo not include Unicode characters. (As I showed in Section 10.1, theimport section of the image file specifies the names of implicitly linkedlibraries as 8-bit strings, so an executable with a Unicode name cannotbe accommodated within the E32 image format.)When the loader searches for an executable, if it gets a possible ‘‘hit’’on a cached root name entry, it further populates the entry (if it hasn’talready done so) opening the file and reading the image header to extractthe following file data:• UID triplet• Module version• Security information• Export bitmap – for V-formatted headers (see Appendix 2, TheE32ImageHeader ).THE LOADER SERVER399The cache can contain file entries for up to six different directories.
Itmaintains all the file entries for these directories for every mounted driveon the system. It also stores the path to each directory in a linked list.The class diagram for the loader cache is shown in Figure 10.1. Weencapsulate each cached directory by a TDirectoryCacheHeaderobject. This has an array of pointers, iCache, to all its cached file entries,each of which is represented by an instance of a TFileCacheRecordclass, holding the rootname of the file and other cached file data. Thepointers are sorted by name, allowing us to perform a binary search for agiven rootname.gDriveFileNameCache[26]TDriveCacheHeader1iDirectoryListnTDirectoryCacheHeaderiCache11iNotify1CCacheNotifyDirChangeTPathListRecordnTFileCacheRecordiPathiNext"\sys\bin"TPathListRecordiNext"\system\bin"Figure 10.1TPathListRecord"\system\programs"Class diagram for the loader cacheWe represent each of the 26 drives by a TDriveCacheHeader object;this has a list: iDirectoryList of all the current cached directorieson it.We encapsulate the path name of every cached directory by a TPathListRecord object; there is a linked list of up to six of these.
In fact,the loader always caches the default directories that it uses. So, in nonsecure mode, it always caches these directories: sys\bin, system\bin,400THE LOADERsystem\programs and system\libs. In secure mode, it alwayscaches the directory sys\bin. Because of this, the loader only recycles the remainder of the six entries between other directories. Becausethe loader tends to search the same paths on each drive, for each directorypath there will generally be a corresponding directory cache on each ofthe active drives.When performing a search, the loader quickly iterates through thepath list, checking whether the directory in question is already cached.Assuming it is, the loader navigates to the corresponding TDirectoryCacheHeader object via the appropriate drive object, where it canthen scan through each of the file entries.The cache must accurately reflect the directory filenames for thosedirectories that it holds.
Any changes to these must trigger a refresh beforethe next query results are returned. The loader uses file server notificationsfor this purpose – one for each drive\path. The CCacheNotifyDirChange class handles change notification. Each time a notification istriggered, the loader destroys that directory cache for the correspondingdrive. This has the effect of forcing a cache reload for that directory whenthe drive\path is next queried.10.3.4 Code and data section managementOn Symbian OS, the low level abstraction describing a segment of codeis the DCodeSeg class, a kernel-side object that I will describe in detailin Section 10.4.1. This class represents the contents of an executablethat has been relocated for particular code and data addresses.
EachDCodeSeg object has an array of pointers to the other code segments towhich it links. In this section I will concentrate on how the loader usesDCodeSeg objects.The code segment object for a non-XIP image file owns a region ofRAM, and it is into this that the loader copies the code and data priorto execution. The kernel allocates space in this RAM region for the codesection, the constant data section, the IAT (if present), the export directoryand the initialized data section, in that order. The loader applies codeand data relocations to this segment.XIP image files, by definition, can have their code executed directlyfrom the image file.
Likewise, the initialized data section and exportdirectory can be read directly. Because of this, there is no need for a RAMregion and instead the DCodeSeg object just contains a pointer to theROM image header for that file.The loader needs to create new code segments and manage those thatit has already created.
For this purpose, it has a private set of executivefunctions, grouped with other loader services into the E32Loader class,which is contained in the user library. Only threads in the file serverTHE LOADER SERVER401process may use these functions – the kernel panics any other threadtrying to use them.Once it has created a code segment for non-XIP media, the kernelreturns the base addresses of the code area and the initialized data areawithin the segment to the loader. These are known as the code load anddata load addresses respectively, and the loader uses them when copyingin the various sections from the image file. The kernel also returns thecode and data run addresses, and the loader uses this information whenapplying code and data relocations.To conserve RAM, the kernel shares code segments whenever possible,and generally only one is required for each executable, even whenthis is shared between processes – I list the only exceptions later, inSection 10.4.1.
So, for example, where a process has been loaded twice,generally the kernel will create only one main code segment for thatprocess. Both processes have their own separate global data chunks,however. When each of these processes is first run, the contents of theinitialized data section is copied out of the code segment into this globaldata chunk.10.3.5 Loader classesIn Figure 10.2 I show the main classes that the loader server uses. TheE32Image class encapsulates a single executable image. When startinga process or dynamically loading a DLL, the loader creates an instanceTLdrInfoiRequestedUids : TUidTypeiHandle : TIntiRequestedVersion : TUint32TCodeSegCreateInfoiHandle : TAny*TProcessCreateInfoRLdrReqiFileName : HBufC8*iPath : HBufC8*E32ImageiReqiRomImageHeaderRImageFinderTRomImageHeaderFigure 10.2 Class diagram for the loader serveriHeaderE32ImageHeader402THE LOADERof this class for the main executable and each statically linked DLL.
(Fornon-XIP media, that includes every implicitly linked DLL, for XIP media,it only includes those which have static data.) The E32Image class has apointer to a header object appropriate to the type of executable – eithera TRomImageHeader object for XIP or an E32ImageHeader objectfor non-XIP media. The object denoted by iHeader holds a RAM copyof the entire E32ImageHeader, whereas iRomImageHeader pointsdirectly to the original header in ROM.The E32Image class is derived from the class TProcessCreateInfo, which adds information that is only required when creating a newprocess – such as stack size, heap size and so on. In turn, TProcessCreateInfo is derived from TCodeSegCreateInfo, which is usedto assemble the data required to create a DCodeSeg object.
The datamember iHandle is a pointer to corresponding kernel-side DCodeSegobject and is used as a handle on it.The RLdrReq class encapsulates a request on the loader. This is usedto store request information copied from the client thread, such as thefilename of the executable to load or a path list to search. It is derivedfrom the TLdrInfo class, which is used to pass request arguments fromclient to server and, if a load request completes successfully, to return ahandle on a newly loaded executable back to the client.The loader uses the RImageFinder class when searching for aparticular image file. To perform a search, this uses a reference to anRLdrReq object, iReq, which is the specification for the file beingsearched for.10.3.6Loading a process from ROMLet us look at the steps involved in loading a process – first looking at themore straightforward case where this is being loaded from ROM.10.3.6.1 Issuing the request to the serverThe client calls the following user library method (or an overload of it):TInt RProcess::Create(const TDesC &aFileName, const TDesC &aCommand,const TUidType &aUidType, TOwnerType aType)The argument aFileName specifies the filename of the executable toload.
The descriptor aCommand may be used to pass a data argumentto the new process’s main thread function. The argument aUidTypespecifies a triplet of UIDs which the executable must match and aTypedefines the ownership of the process handle created (current process orcurrent thread).THE LOADER SERVER403This method creates an RLoader session object and calls its Connect() method, which results in a connect message being sent to theserver. Once the connection is established, control returns to the clientthread. This then calls RLoader::LoadProcess(), which assemblesthe arguments passed from the client into a message object and sends thisto the server.
The kernel then suspends the client thread until the loadercompletes the request.On receipt of the message, the server calls the request servicing functionbelonging to the relevant session, CSessionLoader::ServiceL().This creates an RLdrReq, a loader request object, and populates it withdata such as the filename, command descriptor and so on, reading thesefrom the client thread. Next it allocates an E32Image object for themain process executable and calls the LoadProcess() method on it.Figure 10.3 illustrates this first phase.client thread loader threadRProcess::CreateCSessionLoader::ServiceLRLoader::ConnectE32Image::LoadProcessRLoader::LoadProcessCServerLoader::NewSessionLuserkernelFigure 10.3Issuing the request to the loader server10.3.6.2 Locating the process executableThe next task to be performed is to search for the main executable file.The loader creates an RImageFinder object and sets its member iReqto the newly created request object, which contains the data specifyingthe executable.