You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

38KB

Developing Your Own 32-Bit Operating System — Chapter-04: Interprocess Communications

  • Source: /library/Developing Your Own 32-Bit Operating System/source-file.pdf
  • PDF pages: 63–76
  • Pages without text: 63

Interprocess Communications

H Architecture and General Theory This chapter introduces you to some of your options for Interprocess Communications (IPC). As I mentioned in earlier chapters, you should have a good idea what your operating system will be used for before you determine such important things as its tasking model and what forms of interprocess communications it will use. As you will see in this chapter, the IPC mecha- nisms play a very important pan in the overall makeup of the system. You will also notice that even though I covered task scheduling in the previous chapter, I can't get away from it here because it's tied in very closely with interprocess communications. Messaging and Tasks As described earlier, a task is a single thread or series of instructions that can be independently scheduled for execution. How the operating system decides when a task is ready to execute, where it waits, or is suspended when it can't run, and also how it is scheduled will all depend on the messaging facilities provided by the system. Synchronization Synchronization of the entire system is what IPC all boils down to. On a single CPU, only one task can execute at a time. Whether a task is ready to run will depend on information it re- ceives from other tasks (or the system) and also on how many other tasks are ready to run (multiple tasks competing for CPU time). Looking at this synchronization from the application programmer's point of view, he or she doesn't see most of the action occurring in the system, nor should he or she have to. When a programmer makes a simple system call for a resource or I/O, the task may be suspended until that resource is available or until another task is done with it. Some resources may be available to more than one task at a time, while others are only available to a single task at any one point in time. An example of a resource that may only be available for one task at time is a system memory- allocation routine. Of course, everyone can allocate memory in an operating system, but more than likely it will be managed in such a way that it can be handed out to only one task or pro- gram at a time. In other words, portions of the code will not be reentrant. I discuss reentrancy in a later section of this chapter. In Chapter 3, “The Tasking Model,” I mentioned that a task switch can occur at just about any time. This means that unless interrupts are suspended while one task is in the middle of a memory-allocation routine, this task could be suspended and another task could be started. If the memory-allocation routine was in the middle of a critical piece of code (such as updating a linked list or table) and the second task called this same routine, it could cause some serious problems. This is one place where the IPC mechanism comes into play and is very important. You can't suspend interrupts for extended periods of time. I discuss this also in detail later in this chapter.

Chapter 4 ■ Interprocess Communications Semaphores One common IPC mechanism is called a semaphore. As the name implies, semaphores are used for signaling and exchanging information. Semaphores are code routines and associated data structures that can be accessed by more than one task. In operating systems that use semaphores, there are public calls that allocate semaphores, per- form operations on the semaphore structures, and finally destroy (deallocate) the semaphore and its associated structures when they are no longer required. A semaphore is usually assigned a system-wide unique identifier that tasks can use to select the semaphore when they want to perform an operation on it. However, some systems allow pri- vate semaphores that only related tasks can access. This identifier is generally associated with the ID of the task that created it, and more than likely it will be used to control access to one or more functions that the owner task provides. This allows you to lock out tasks when neces- sary to prevent simultaneous execution on non-reentrant code. The semaphores play traffic cop. They are used also to signal the beginning or ending of an event. Typical semaphore system calls are semget( ), which allocates a semaphore; semctl( ), which controls certain semaphore operations; and semop(), which performs operations on a sema- phore. The arguments (or parameters) to these calls usually include the semaphore ID (or an indication of where to return the ID if it's being allocated), what operation you want to per- form, and a set of flags that indicate specific desires or conditions that your task wants to ad- here to, such as one that says “Don't suspend me if I can't get by you” or “I'll wait till I can get it.” In most cases, the application programmer won't even know that a semaphore operation is being performed. He or she will make a system call for some resource, and the library code or code contained in the system will perform the semaphore operation that ensures synchronized access to its code (if that's its purpose) or that ensures some other required condition on the system is met before the calling task is scheduled to execute. When a semaphore operation is performed and the calling task is suspended, it is waiting at that semaphore for a condition to change before proceeding (being restarted to execute). As you can see, in order to work properly, the semaphore mechanism would have to be coupled with all of the other kernel code, such as those procedures that create new tasks, destroy tasks, or schedule tasks for execution. I opted not to use semaphores in my operating system. I studied how they were used in UNIX systems and in OS/2, and it was my opinion that they added an unnecessary layer of complex- ity. Furthermore, all of the functions that they perform are available with simpler messaging facilities. Another reason I avoided semaphores is that they are usually associated specifically with tasks (processes). In my system, tasks are not wholly independent. They rely on a certain data con- tained in structures that are assigned to a program or job that will consist of one or more tasks. H

Part II ■ Architecture and General Theory If you intend to implement semaphores, you should study how they are implemented in the more popular systems that use them (such as UNIX, OS/2, and so on). One thing to note is that portability may be an issue for you. Semaphore functions on some systems are actually implemented as an installable device instead of as an integrated pan of the kernel code. In my humble opinion, I think they are best implemented as tightly integrated pieces of the kernel code. This is much more efficient. Pipes Another popular IPC mechanism is called a pipe. Pipes are pseudo input/output devices that can be used to send or receive data between tasks. They are normally stream-oriented devices, and the system calls will look very much like file-system operations. Pipes can be implemented as public devices, which means they can be given names so that unrelated tasks can access them. Quite often they are not public and are used to exchange data between tasks that are part of one program. Common system calls for pipe operations would include CreatePipe ( ) , OpenPipe ( ) , ClosePipe ( ) , ReadPipe( ), and WritePipe( ). The parameters to these calls would be very similar to equiva- lent file operations. In fact, some systems allow standard I/O to be redirected through pipes. OS/2 is one such system. If you intend to implement pipes as part of your IPC repertoire, you should study how they are used in other systems. I initially considered public named pipes for MMURTL to allow data transfer between system services and clients, but I used a second form of messaging instead. If you're not worried about size or complexity in your system, public pipes are very handy. They also don't have to be so tightly coupled with the kernel code. Messages Messaging methods can be implemented in several different ways (although not in as many different ways as semaphores), as follows: Send and Wait The most rudimentary form of messaging on any system is a pair of system calls generally known as send ( ) and wait ( ) . You will see these commands, or similar ones, on most real-time operat- ing systems. It is even possible to implement messaging routines using semaphores and shared memory. This would provide the needed synchronization for task management on the system. But once again, you must keep the code-maintenance aspects in mind for whatever implemen- tation you choose.

Chapter 4 ■ Interprocess Communications The best method to implement messaging (and I don't think I'll get an argument here) is to make it an integral part of the kernel code in your system. Indeed, the entire kernel should be built around whatever messaging scheme you decide on. With the send( ) and wait ( ) types of messaging, the only other resource required is somewhere to send a message and a place to wait for one (usually the same place). The best way I can illustrate messaging is to provide you the details of my own messaging implementation. This will afford an overview of messaging for the novice as well as the details for the more-experienced systems programmer. In the system I've included for you (MMURTL), a program is made up of one or more tasks. I refer to a program as a job. The initial task in a program may create additional tasks as needed. These new tasks inherit certain attributes from the initial task. I am describing this because you may decide to implement tasks as wholly independent entities. In a message-based system such as MMURTL, tasks exchange information and synchron- ize their execution by sending messages and waiting for them. On many message-based systems for smaller embedded applications, only the basic form of messaging is implemented. I have added a second, more-powerful form of messaging, which adds two more kernel message functions: request ( ) and respond ( ) . This now gives MMURTL two forms of messaging, one which is a “request” for services, which should receive a response, and a non-specific “message” that doesn't expect a response. The request/respond concept is what I consider the key to the client-server architecture that was one of my original goals. Other operating systems use this type of messaging scheme also. Sending and receiving messages in any message-based system is not unlike messaging of any kind (even phone messages). You can send one-way messages to a person, such as, “Tell Mr. Zork to forget it. His offer is the pits.” This is an example of a one-way message, for which you expect no response. One key element in a message system is where you leave your messages. In Bob Zork's case, he will get the message from his secretary. In an operating system's case, it is generally an allocated resource that can be called a mailbox, a bin, or just about any other name you like. It is a place where you send messages or wait for them to exchange information. In MMURTL's case, I called it an exchange. In order to send or receive a message, you must have an exchange. In your system, you will provide a function to allocate a mailbox, an exchange point, or whatever you call it. MMURTL provides a call to allocate exchanges for jobs. It is called AllocExch( ) and is defined as follows in C: unsigned long AllocExch(long *pdExchRet); The parameter pdExchRet points to a dword (32-bit unsigned variable) where the exchange num- ber is returned. The function return value is the kernel error if there is one (such as when there are no more exchanges). It returns 0 if all goes well. You also need a way to send the message. Operating-system calls to send a message come in several varieties. The most common is the send ( ) function I described earlier. I implemented H

//- I Part II ■ Architecture and General Theory this as SendMsg ( ) . It does just what it says — it sends a message. You tell it what exchange, give it the message, and away it goes. The task that you want to receive the message may be “wait- ing” at the exchange by calling the wait ( ) function I described earlier. In MMURTL I call this WaitMsg ( ) . If there is no task waiting at that exchange, the message will wait there until a task waits at the exchange or checks the exchange with CheckMsg(). This is an added messaging function to allow you to check an exchange for a message without actually waiting. The C definitions for these calls and their parameters as I have implemented them are as follows: unsigned long SendMsg(long dExch, long dMsgPartl , long dMsgPart2) ; The dExch is the destination exchange. The dMsgPartl and dMsgPart2 are the two dwords in the message. Note that my messages are two 32-bit values — not too large to move around, but large enough for a couple of pointers. unsigned long WaitMsg(long dExch, char *pMsgRet) ; unsigned long CheckMsg(long dExch, char *pMsgRet) ; The dExch is the exchange where you will wait to check for a message. The pMsgRet points to an eight-byte (two dwords) structure where the message will be placed. Did you notice (from the preceding definitions) that not only messages wait at exchanges, but tasks can wait there, too? This is an extremely important concept. Consider the example of the telephone again. The task is the human, the answering machine is the exchange. You can leave a message on the machine (at the exchange) if no one (no task) is waiting there. If a human is there waiting (a task is at the exchange, waiting), the message is received right away. Now consider this: In a single processor system that is executing a multitasking operating sys- tem, only one task is actually executing instructions. All of the other tasks are waiting some- where. There are only two places where a task can wait in MMURTL: at an exchange or on the Ready queue. The Ready queue is the line-up of tasks that are in a ready-to-run state but are not run- ning because there's a higher-priority task currently executing. Here's one more quick topic to round off what I've already covered. Tasks are started with the kernel primitives SpawnTask or NewTask. You point to a piece of code, provide some other pieces of information, and voila, a task is born. Yes, it's a little more complicated than that, but you have enough information to understand the basic theory. Now I have defined some very important terms and functions — not in detail yet, but enough that I can talk about them. SendMsg ( ) , CheckMsg ( ) , WaitMsg ( ) , SpawnTask ( ) , and NewTask ( ) are five very important kernel primitives. AllocExch ( ) is an important auxiliary function. The only

Chapter 4 ■ Interprocess Communications H reason this is discussed with the rest of the kernel primitives is because of its importance. I don't consider it part of the kernel, because it has no effect on the tasking model (CPU time alloca- tion). You also know now about exchanges and the Ready queue. I apologize that none of the items I've introduced here have names that are confusing or based on buzzwords. I'll try to liven it up later on. You now have enough information under your belt to provide a scenario of message passing that will help enlighten you to what actually occurs in a message-based system. Table 4. 1 shows actions and reactions between a program and the operating system. You start with the execution of a single task. What it's doing isn't important. Imagine that as you move down the table in the example, time is passing. In this example, whenever you call a kernel primitive, you enter The Kernel Zone. (Just kidding. It's not called The Kernel Zone — just the “kernel.") Table 4.1. Task and kernel interaction. Task Action Kernel Action Taskl is running. Taskl allocates Exchl. Taskl calls SpawnTask (to start Task2). Task2 is running. Task2 allocates Exch2. Task2 sends a message to Exchl. Task2 is still running. Task2 calls WaitMsg at Exch2. Kernel checks priority of new task. Task2 is higher. Kernel places Taskl on the Ready queue. Kernel makes Task2 run. Kernel checks for a task waiting at Exchl. None are waiting. Kernel attaches message to Exchl. Kernel evaluates Ready queue to see what runs next. It's still Task2. Kernel checks for a message waiting at Exch2. None found. Kernel places Task2 on Exch2.

48 | Part II ■ Architecture and General Theory Table 4.1. continued Task Action Kernel Acti Taskl is running. Taskl sends a message to Exch2. Task2 is running. Kernel evaluates Ready queue. Taskl is ready to run. Kernel makes Taskl run. Kernel checks for task at Exch2 and finds Task2 there. It gives Task2 the message. Kernel places Taskl on Ready queue. Kernel makes Task2 run. (It's a higher priority.) From the simple example in Table 4. 1 , you can see that the kernel has its job cut out for it. You can also see that it is the messaging and the priority of the tasks that determine the sharing of CPU time (as was described in previous chapters). From the example you can also see that when you send a message, the kernel attaches the message to an exchange. If there is a task at the exchange, the message is associated with that task and it is placed on the Ready queue. The Ready queue is immediately reevaluated, and if the task that just received the message is a higher priority than the task that sent it, the receiving task is made to run. What would happen if both Taskl and Task2 waited at their exchanges and no one sent a message? You guessed it — the processor suddenly finds itself with nothing to do. Actually it really does absolutely nothing. In some systems, dummy tasks are created with extremely low priorities, so there is always a task to run. This can simplify the scheduler. This low-priority task can even be made to do some chores, such as garbage collection or a system-integrity check. I didn't do this with MMURTL. I actually halt the processor with interrupts enabled. If every- one is waiting at an exchange, they must be waiting for something. More than likely it is some- thing from the outside world, such as a keystroke. Each time an interrupt occurs, the processor is activated and it checks the Ready queue. If the interrupt that caused it to wake up sent a message, there should be a task sitting on the Ready queue. If there is one, it will be made to run. This may not work on all types of processors. You will have to dig into the technical as- pects of the processor you are using to ensure that a scheme like this will work. RequestQ I have added two more messaging types in my system. If a very small embedded kernel is your goal, you will more than likely not use these types in your system. They are dedicated types.

Chapter 4 ■ Interprocess Communications Request ( ) and Respond ( ) form the basis for a client-server system that provides for identifica- tion of the destination exchange and also allows you to send and receive much more than the eight-byte message used with the SendMsg ( ) primitive. The Request ( ) and Respond ( ) messaging primitives are designed so you can install a program called a system service, which provides shared processing for all applications on the system. The processing is carried out by the service, and a response (with the results and possibly data) is returned to the requester. Message-based services are used to provide shared processing functions that are not time criti- cal (where a few-hundred-microsecond delay would not make a difference), and also need to be shared with multiple applications. They are ideal for things such as file systems, keyboard input, printing services, queued file management, e-mail services, BBS systems, fax services, the list could go on and on. The MMURTL FAT-file system and keyboard are both handled with system services. In fact, you could implement named pipes (discussed earlier in this chap- ter) as a system service. With MMURTL, each service that is installed is given a name. The name must be unique on the machine. When the service is first installed, it registers its name with the OS Name Regis- try and tells the OS what exchange it will be serving. This way, the user of the system service doesn't need to know the exchange number, only the service name. The exchange number may be different every time it's installed, but the name will always be the same. A fully loaded sys- tem may have 5, 10, or even 15 system services. Each service can provide up to 65,533 differ- ent functions as identified by the service code. The functions and what they do are defined by the service. The OS knows nothing about the service codes (except for one discussed later). It doesn't need to, because the Request ( ) interface is identical for all services. The interface to the Request ( ) primitive is procedural, but it has quite a few more parameters than SendMsg ( ) . Look at it prototyped in C: unsigned long Request (char *pSvcName, unsigned int wSvcCode, long dRespExch, long *pdRqHndlRet, long dnpSend char *pData1SR, long dcbDatalSR, char *pData2SR, long dcbData2SR, long dDataO, long dDatal , long dData2 ) ; At first glance, you'll see that the call for making a request is a little bit more complicated than SendMsg ( ). But, as the plot unfolds you will see just how powerful this messaging mechanism can be (and how simple it really is). Consider each of the parameters: pSvcName Pointer to the service name you are requesting. The service name is eight characters, all capitals, and space-padded on the right. H

H cq I Ptfrf 77 ■ Architecture and General Theory wSvcCode Number of the specific function you are requesting. These are documented independently by each service. dRespExch Exchange that the service will respond to with the results (an exchange you have allocated). pdRqHndlRet Pointer where the OS will return a handle to you that is used to identify this request when you receive the response at your exchange. This is needed because you can make multiple requests and direct all the responses to the same exchange. If you made more than one request, you'll need to know which one is responding. dnpSend The number (0, 1, or 2) of the two data pointers that are moving data from you to the service. The service already knows this, but network transport mechanisms do not. If pSendi was a pointer to some data the service was getting from your memory area and pSend2 was not used or was pointing to your memory area for the service to fill, dnpSend would be 1. If both data items were being sent to you from the service, this would be 0. pDatal Pointer to memory in your address space that the service must access (either to read or write as defined by the service code). For instance, in the file-system OpenFile( ) function, pDatal points to the file name (data being sent to the service). This may even point to a structure or an array, depending on how much information is needed for this particular function (service code). dcbDatal How many bytes the pDataSend points to. Using the 0penFile( ) example, this would be the size (length) of the filename. pData2 This is a second pointer, exactly like pDatal described previously. dcbData2 This is the same as dcbDatal . dDataO, dDatal , dData2 These are three dwords that provide additional information for the service. In many functions you will not even use pDatal or pData2 to send data to the service, but will simply fill in a value in one or more of these three dwords. These can never be pointers to data. This will be explained later, in the discussion of memory management. RespondQ The Respond ( ) primitive is much less complicated than Request ( ). This doesn't mean the sys- tem service has it easy. There's still a little work to do. Here is the C prototype: Respond(long dRqHndl, long dStatRet); The parameters are also a little less intimidating than those for Request ( ) . They are as follows: dRqHndl The handle to the request block that the service is responding to. dStatRet The status or error code returned to the requester.

Chapter 4 M Interprocess Communications A job (your program) calls Request ( ) and asks a service to do something, then it calls WaitMsg ( ) and sits at an exchange waiting for a reply. If you remember, the message that comes in to an exchange is an eight-byte (two dwords) message. Two questions arise: 1 . How do you know if this is a response or just a simple message sent here by another task? 2. Where is the response data and status code? First, the content of the eight-byte message tells you if it is a message or a response. The con- vention is the value of the first dword in the message. If it is 80000000h or above, it is not* response. Second, this dword should match the request handle you were provided when you made the Request ( ) call. (Remember pRqHndlRet?) If this is the response, the second dword is the status code or error from the service. Zero (0) usually indicates no error, although its exact meaning is left up to the service. Second, if the request was serviced (with no errors), the data has already been placed into your memory space where pDatal or pData2 was pointing. This is possible because the kernel pro- vides alias pointers to the service into your data area to read or write data. Also, if you were sending data into the service via pDatal or pData2, the kernel has aliased this pointer for the service as well, and the service has already read your data and used it as needed. Not as difficult as you expected, right? But let me guess — this aliasing thing with memory ad- dresses is still a bit muddy to you. A little further into this chapter I'll cover memory manage- ment as it applies to messaging, which should clear things up considerably. Link Blocks I keep referring to how a message or a task just “sits” or “waits” at an exchange. An exchange is a small structure in memory. You will need some way (a method) to attach messages to your mailbox. It will no doubt be a linked list of sorts. I opted to use something called a link block (LB). A link block is a little structure (smaller than an exchange) that becomes a link in a linked list of items that are connected to an exchange. Link blocks are not very big, but still very important. (You will find out how important they are if you run out of them!) There is one link block in use for every outstanding (unanswered) request and one for every message waiting at an exchange. This can add up to hundreds in a real hurry. m Reentrancy Issues A multitasking system that lets you simply point to a set of instructions and execute them means that you can actually be executing the same code at almost the same time. I say “almost” be- cause really only one task is running at a time.

H Part II ■ Architecture and General Theory This can lead to serious problems if the code being executed is not designed to be reentrant. Reentrant means that if a task is executing a series of instructions and gets preempted, or if it pauses in the middle of them, it's OK for another task to enter and execute the same code be- fore the first task is done with it. To really understand this issue, let's look at an example. I'll use a pseudo-text algorithm to define a series of instructions that are wow-reentrant and show you what can happen. The ex- ample is a function that allocates buffers and returns a pointer to it. It's called GetMeSomeMem( ). In this example, the code accesses two variables to carry out its mission. Variable nBuffersLeft Variable pNextBuffer GetMeSomeMem() If (nBuffersLeft > 0) (A place between instructions called PointX) { (Allocate a Buffer) Decrement nBuffersLeft Increment pNextBuffer Return pNextBuffer } else Return Error Let's suppose two tasks are designed to call this function. As you know, only one task can really be running at a time, even in a multitasking system. So, Taskl calls GetMeSomeMem( ) . But when it reaches PointX, it is preempted. For the sake of understanding this, there was only one buffer left. Now, Task2 is running and calls GetMeSomeMem ( ) . It successfully makes it all the way through and gets to that last buffer. Now, Taskl gets to run again. He is restarted by the scheduler and starts up at PointX. The code has already checked and there was one buffer left. So the code goes in and whammo! — it's crash time. The buffer is gone; pointers are off the ends of arrays; it's a mess. As you can see, some method is required to prevent two tasks from being inside this code at the same time. Semaphores are used on many systems. Each task that enters the code checks the semaphore and will be suspended until the last task is done with it. The call to manage and check the semaphore would actually be pan of the allocation code itself. Messages can also provide exactly the same functionality. Allocate a mailbox or exchange and send it one mes- sage. Each task will wait ( ) at the exchange until it has a message. When each task is done with the allocation routine, it sends back the message so the next task can get in. It's really quite simple. What's usually not so simple for the operating-system writer is ensuring that you identify and protect all of the non-reentrant code. If the instruction sequence is short enough, you can simply disable interrupts for the critical period of time in the code. You should always know how long this will be, and it shouldn't be

Chapter 4 M Interprocess Communications too long. If you disable interrupts for too long, you will suffer interrupt latency problems. You will lose data, as discussed forthwith. S Interrupt Latency Interrupt latency is when interrupts don't get serviced fast enough, and maybe even not often enough. I mentioned in the preceding section that you shouldn't disable interrupts for too long a period of time. Just to give you an idea of what periods of time I'm talking about, look at a real-world example. A nonbufifered communication UART (Universal Asynchronous Receiver Transmitter) operating at 38,400 bits per second will interrupt every 208 microseconds. This is 1/38,400 x 8 because they will interrupt for every byte (8 bits). The typical processor run- ning at 25MHz executes most of its instructions in 2 or 3 system-clock periods. That would be an average of 120 nanoseconds (1/25,000,000 X 3). In theory, this means you could execute as many as 1 ,730 instructions in the interrupt interval. Whoa there — that was only in theory! Now you have to do a reality check. You must take into consideration that there are more interrupts than just that communications channel. The timer interrupt will be firing off every so often. The communications interrupt itself will have inter- rupts disabled for a good period of time, and there is also “the biggie”: task switches. Why are task switches considered “the biggie”? A task switch can consume as many as 30 or 40 microseconds. The actual amount of time will depend on whether you use hardware task switch- ing or do it yourself, but you will still have interrupts disabled for a rather scary length of time. Also, if you remember the GetMeSomeMem( ) example, you know you locked out multiple users with the kernel messaging (or semaphores). You didn't disable interrupts. In the kernel code, you can't use the kernel code to lock things out of the kernel. You must disable interrupts in many sections. It will be the only way. Good, tight code in the kernel is a must. I could probably tighten mine a whole lot, but my testing shows I did OK. You will have to do some serious testing on your system to ensure interrupt latency is not going to be a problem. Have you ever run a communications program for an extended period of time at 19,200 baud in Windows 3.x? You will lose data. Don't blame Windows, though. It's doing some things I personally consider impossible. Memory Management Issues The last hurdle in interprocess communications is memory. It doesn't sound like it would be a problem, but it can be, depending on how you implement your memory model. If you use a completely flat model for the entire system, it will simplify IPC greatly — but it will increase the headaches elsewhere. A huge linear array of memory in your system that is shared by all the programs and tasks means they all understand a pointer to any memory address. This

H Part II ■ Architecture and General Theory in turn means you can freely pass pointers in messages back and forth (even between different programs) and no one gets confused. If you implement independent linear-memory arrays through paging hardware (as I did with MMURTL), then you have to find a way to translate (alias) addresses that are contained in messages that will be passed between different programs. Segmented memory models will suffer from this aliasing requirement to an even greater ex- tent. I recommend you avoid a fully segmented model. I started with one but abandoned it early on. Fully segmented models allow any memory allocation to be zero-based. This means that if paging is used along with the segmentation, it will be even a greater chore to translate addresses that are passed around on the system. You will learn about memory-management options in Chapter 5, “Memory Management.”

Powered by TurnKey Linux.