Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Operating Systems Design and Implementation Chapter 02 (version January 30, 2008) Melanie Rieback Vrije Universiteit Amsterdam, Faculty of Sciences Dept. Computer Science Room R4.23. Tel: (020) 598 7874 E-mail: [email protected], URL: www.cs.vu.nl/∼melanie/ 01 02 03 04 05 00 – 1 Introduction Processes Input/Output Memory Management File Systems / Processes • Processes, introduction • Interprocess communication • IPC problems • Scheduling • Processes in MINIX • Implementation in MINIX 02 – 1 Processes/ Processes Primitive model: a process represents a user or application and executes a program on behalf of its owner. Data involved in this processing is retrieved from and stored on files. Data in files persist over processes. A process is an invention by an operating system: it is used as a placeholder for executing programs so that we can keep a precise account of how program execution is affecting the use of hardware and software resources. process = program in execution 02 – 2 Processes/2.1 Introduction to Processes Concurrent Processes Important: processes are, in principle, mutually independent. This implies that the CPU can be allocated in turn to different processes: One program counter Process A Four program counters Process switch B C A B C D D C B A D (a) Time (b) (c) • You don’t know when the operating system decides to allocate the CPU to a next process. • Processes are independent: they need explicit means to communicate with each other. • Always remember: a process executes a program; a process is not the same as a program. 02 – 3 Processes/2.1 Introduction to Processes Process Hierarchies Basic problem: How do you actually create processes? In many systems, the operating system creates just one initial process. Solution: offer primitives that allow a process to create another process ⇒ leads to a hierarchy of processes: A B D E C F Question: What would happen when a parent process dies? 02 – 4 Processes/2.1 Introduction to Processes Process States Basic idea: If the operating system is to allocate resources such as the CPU to processes, it will have to select suitable processes for allocation ⇒ we need to keep track of the state of a process: Running 1 Blocked 3 2 4 Ready 1. Process blocks for input 2. Scheduler picks another process 3. Scheduler picks this process 4. Input becomes available • Running: the process is currently executed by the CPU • Blocked: the process is waiting for a resource to come available • Ready: the process is ready to be selected The scheduler takes decisions on (de)allocation of the CPU (transitions 2 & 3). Question: How can transitions 1 & 4 take place? 02 – 5 Processes/2.1 Introduction to Processes Scheduler vs Processes Note: it is the scheduler who decides to (de)allocate the CPU. This leads to a simple process organization: Processes 0 1 n–2 n–1 Scheduler Question: Does this organization have anything to do with the hierarchical organization of processes? 02 – 6 Processes/2.1 Introduction to Processes Process Implementation To implement processes, the OS keeps track of the process’ state, values of registers, memory usage, etc. In MINIX: Kernel Process management File management Registers Program counter Program status word Stack pointer Process state Current scheduling priority Maximum scheduling priority Scheduling ticks left Quantum size CPU time used Message queue ptrs Pending signal bits Various flag bits Process name Ptr to text segment Ptr to data segment Ptr to bss segment Exit status Signal status Process ID Parent process Process group Children’s CPU time Real UID Effective UID Real GID Effective GID File info for sharing text Bitmaps for signals Various flag bits Process name UMASK mask Root directory Working directory File descriptors Real UID Effective UID Real GID Effective GID Controlling TTY Save area for read/write System call parameters Various flag bits Question: Why do we see redundancy in maintaining states ? 02 – 7 Processes/2.1 Introduction to Processes Interrupt Handling Basic idea: To deallocate the CPU in favor of the scheduler, we use hardware support – whenever the hardware generates an interrupt (e.g., on account of a timer), the scheduler “automagically” gets control. • Associated with each I/O device is a memory location where execution continues when that device generates an interrupt (interrupt vector). • The interrupt vector contains the start address of an operating-system provided procedure (interrupt handler). Execution continues with that procedure. 02 – 8 Processes/2.1 Introduction to Processes Interrupt Handling & Scheduling 1. 2. 3. 4. 5. 6. 7. 8. 9. Hardware stacks program counter, etc. Hardware loads new program counter from interrupt vector Assembly language procedure saves registers Assembly language procedure sets up a new stack C interrupt service constructs and sends message Message-passing code marks waiting message recipient ready Scheduler decides which process is to run next C procedure returns to the assembly code Assembly language procedure starts up new current process Note: Every time an interrupt occurs, the scheduler will eventually get control. It acts as the big mediator. A process can normally not give the CPU to another process without going through the scheduler. 02 – 9 Processes/2.1 Introduction to Processes Threads Idea: Sometimes you want something as a process in order to structure your application, but you don’t want the overhead that goes with it ⇒ make use of threads. Basic idea: Threads also share resources, but only within the same process ⇒ resource management is completely left outside the control of the operating system. 02 – 10 Processes/2.1 Introduction to Processes Threads: Minimal Support Important: threads fall under the regime of a single process, and thus reside in the same address space ⇒ all information exchange is through data shared between the threads. • Each thread has its own stack, processor context, and state; • Threads should synchronize through simple primitives (semaphores, monitors); • Each thread may call upon any service provided by the operating system; it does so on behalf of the process to which it belongs. Computer Program counter Computer Thread (a) Process (b) Question: What’s so light-weight about all this? Where do processes and threads really differ? 02 – 11 Processes/2.1 Introduction to Processes Threads – Some Problems • Does the OS keep an administration of threads (kernel threads), or not (user threads)? Question: What happens if a user-space thread does a blocking system call? • What to do when you clone a process: does the new process get all the threads as well? What about threads currently blocking on a system call? • When the OS sends a signal, how can you relate that signal to a thread? Should you relate it to a thread? Conclusion: Designing threads is maybe just a bit more tedious than you would expect at first thought. 02 – 12 Processes/2.1 Introduction to Processes Interprocess Communication The essence: Processes need to communicate with each other: • Data needs to be exchanged between processes • We have to synchronize processes to avoid they get in each other’s way • We need to synchronize processes on account of dependencies 02 – 13 Processes/2.2 Interprocess Communication Race Conditions Spooler directory Process A 4 abc 5 prog. c 6 prog. n 7 out = 4 in = 7 Process B • Process A reads in = 7, and decides to append its file at that position • Process A is suspended by the OS (because its time slot expired) • Process B also reads in = 7, puts its file at that position. It then sets in to 8 and eventually gets suspended • Process A writes its file to position 7 Problem: reading and updating in should be an atomic action. If it’s not, processes can race each other and come to the wrong conclusions. 02 – 14 Processes/2.2 Interprocess Communication Mutual Exclusion Critical region: A piece of shared memory (like a common variable) for which we have: 1: No two process may be simultaneously in their critical regions 2: No assumptions may be made about speeds or number of CPUs 3: No process running outside its critical region may block other processes 4: No process should have to wait forever to enter its critical region (Non)solutions: • Disable interrupts: simply prevent that the CPU can be re-allocated. Pretty blunt. Works for singleCPU systems only. • Lock variables: Same as with the spooler variable in: you’re just too late to give the variable its new value 02 – 15 Processes/2.2 Interprocess Communication Mutual Exclusion Strict Alteration while(TRUE) { while(turn != 0); critical_region(); turn = 1; noncritical_region(); } while(TRUE) { while(turn != 1); critical_region(); turn = 0; noncritical_region(); } Question: Which assumption is implicitly being made? Note: This is again a nonsolution: it violates the condition that processes may not block each other. 02 – 16 Processes/2.2 Interprocess Communication Mutual Exclusion – Peterson #define FALSE 0 #define TRUE 1 #define N 2 int turn; int interested[N]; void enter_region(int process) { int other; other = 1 - process; interested[process] = TRUE; turn = process; while(turn == process && interested[other]); } void leave_region(int process){ interested[process] = FALSE; } Note: This solution can “easily” be extended to cover N > 2 processes. 02 – 17 Processes/2.2 Interprocess Communication Mutual Exclusion The TSL Instruction Finally: Software solutions are just no good in the general case. Instead, we can actually implement software into the hardware, and offer the result as a separate instruction: enter region: tsl register,lock cmp register,#0 jne enter region ret | copy lock to register and set lock to 1 | was lock zero? | if it was non zero, lock was set, so loop | return to caller; critical region entered leave region: move lock,#0 ret | store a 0 in lock | return to caller 02 – 18 Processes/2.2 Interprocess Communication Avoiding Busy Waiting Problem: The solutions so far let a process keep the CPU, busy waiting until it can enter its critical region. Question: With a single CPU, does the other process ever get a chance to continue? Solution: Let a process waiting to enter its critical region return the CPU to the scheduler voluntarily: void sleep(){ enter_region(); set own state to BLOCKED; leave_region(); give CPU to scheduler; } void wakeup(process){ enter_region(); set state of process to READY; leave_region(); give CPU to scheduler; } Question: Why do we need enter region() and leave region() here? And why is it OK to use their busy-waiting implementations? Question: Why would you want to give the CPU back to the scheduler when waking up a process? 02 – 19 Processes/2.2 Interprocess Communication Producer-Consumer #define N 100 int count = 0; void producer(){ int item; while(TRUE){ item = produce_item(); if (count == N) (); insert_item(item); count = count + 1; if (count == 1) (consumer); } } void consumer(){ int item; while(TRUE){ if (count == 0) (); item = remove_item(); count = count - 1; if (count == N-1) (producer); consume_item(item); } } Question: There’s something wrong here – what? 02 – 20 Processes/2.2 Interprocess Communication Semaphores Basic idea: Introduce a special integer type with two operations down and up that operate on the semaphore sema: • down: if sema ≤ 0 then block the calling process until it becomes positive again; otherwise decrement sema by one. • up: if there is a process blocking on sema, wake it up; otherwise increment sema by one. Note: We’re talking about atomic actions here. 02 – 21 Processes/2.2 Interprocess Communication Semaphore – Example #define N 100 typedef int semaphore; semaphore mutex = 1; semaphore empty = N; semaphore full = 0; void producer(){ int item; while(TRUE){ item = produce_item(); (&empty); (&mutex); insert_item(item); (&mutex); (&full); } } void consumer(){ int item; while(TRUE){ (&full); (&mutex); item = remove_item(item); (&mutex); (&empty); consume_item(item); } } 02 – 22 Processes/2.2 Interprocess Communication Monitors Problem: Semaphores have been heavily criticized for the chaos they can introduce in programs. What is generally needed is a more structured approach toward process synchronization ⇒ monitors. Essence: Think of a monitor as a protected object whose methods can be invoked only one at a time 02 – 23 Processes/2.2 Interprocess Communication Monitors – Example ProdCons { condition full, empty; int count = 0; void enter(int item) { if (count == N) (full); insert_item(item); count = count + 1; if (count == 1) (empty); } } void remove(int* item) { if (count == 0) (empty); item* = remove_item(); count = count - 1; if (count == N-1) (full); } void producer(){ void consumer(){ int item; int item; while(TRUE){ while(TRUE){ item = produce_item(); ; ; consume_item(item); } } } } 02 – 24 Processes/2.2 Interprocess Communication Message-Passing Idea: processes communicate by sending and receiving only messages: send( destination, &message ); receive( source, &message ); receive( ANY, &message ); • What to do when the communication channel is unreliable? • How can you verify the identity of the other party (authentication)? 02 – 25 Processes/2.2 Interprocess Communication Message-passing: example #define N 100 void producer(){ int item; message msg; } while(TRUE){ item = produce_item(); receive(consumer, &msg); build_message(&msg, item); send(consumer, &msg); } void consumer(){ int item, i; message msg; } for(i = 0; i < N; i++) send(producer, &msg); while(TRUE){ receive(producer, &msg); item = extract_item(); send(producer, &msg); consume_item(item); } Question: What’s happening here? Are there any assumptions about buffering & blocking? 02 – 26 Processes/2.2 Interprocess Communication Dining philosophers (1/3) Problem: There are five philosophers, each seated at a round table before a bowl of spaghetti. A philosopher needs two forks to eat the spaghetti: the one to her left, and the one to her right. #define N 5 void philosopher(int i){ while(TRUE){ think(); take_fork( i ); take_fork( (i+1) % N ); eat(); put_fork( i ); put_fork( (i+1) % N ); } } 02 – 27 Processes/2.3 Classical IPC Problems Dining philosophers (2/3) Main issue: how to avoid deadlock & starvation. The following solution has no deadlock (why not?): philosopher(int i){ while( TRUE ){ think(); if( i > 0 ) { take_fork( i ); take_fork( (i+1) % N ); } else { take_fork( (i+1) % N ); take_fork( i ); } eat(); put_fork( i ); put_fork( (i+1) % N ); } } Note: There is great code available at http://www.cs.utk.edu/∼mbeck/classes/cs560/ 02 – 28 Processes/2.3 Classical IPC Problems Dining philosophers (3/3) philosopher( int i ) { while( TRUE ) { think(); take_forks( i ); eat(); put_forks( i ); } } take_forks( int i ) { down( &mutex ); state[i] = HUNGRY; test( i ); up( &mutex ); down( &start_eating[i] ); } test( int i ) { if( state[i] == state[LEFT(i)] != state[RIGHT(i)] != state[i] = EATING; up( &start_eating[i] } } put_forks( int i ) { down( &mutex ); state[i] = THINKING; test( LEFT(i) ); test( RIGHT(i) ); up( &mutex ); } HUNGRY && EATING && EATING ){ ); Question: Can there be deadlock or starvation? 02 – 29 Processes/2.3 Classical IPC Problems Readers/writers (1/2) Problem: N processes make use of shared data. It is permitted to let reading processes simultaneously access the data, but only exactly one process for modifications. typedef int semaphore; semaphore mutex = 1; semaphore dbase = 1; int rcount = 0; void reader(){ while(TRUE){ down( &mutex ); rcount = rcount + 1; if( rcount == 1 ) down( &dbase ); up( &mutex ); read_data_base(); down( &mutex ); rcount = rcount - 1; if( rcount == 0) up( &dbase ); up( &mutex ); use_data_read(); } } 02 – 30 void writer(){ while(TRUE){ think_up_data(); down( &dbase ); write_data_base(); up( &dbase ); } } Question: What’s so bad about this solution? Processes/2.3 Classical IPC Problems Readers/writers (2/2) Simple solution: make a FIFO-queue of readers and writers and let several readers in at the same time. Guaranteed free of deadlock and starvation. batch #2 of readers writer batch #1 of readers 02 – 31 Processes/2.3 Classical IPC Problems Process scheduling (1/3) Problem: There are a number of processes ready to execute their associated program. However, there is only one CPU available ⇒ one of the processes has to be selected. (a) Long CPU burst Waiting for I/O Short CPU burst (b) Time Scheduler: part of an operating system that is responsible for deciding which process may execute, and when that process may execute ⇒ scheduling algorithm. 02 – 32 Processes/2.4 Process Scheduling Process scheduling (2/3) All systems: • Fairness: no prejudice with respect to processes. • Policy enforcement: seeing that stated scheduling policy is carried out. • Balance: all parts of the system are kept busy. Batch systems: • Throughput: do as many jobs as possible. • Turnaround time: have jobs processed as quick as possible. • Utilization: keep the CPU busy doing “real” work. 02 – 33 Processes/2.4 Process Scheduling Process scheduling (3/3) Interactive systems: • Response time: respond to requests quickly. • Proportionality: meet user’s expectations. Real-time systems: • Meeting deadlines: avoid losing data. • Predictability: avoid quality degradation in multimedia systems. Question: Suppose we had a really nifty scheduler that could optimize on everything, but it would take some time. Is that useful? 02 – 34 Processes/2.4 Process Scheduling Batch systems (1/2) Shortest Job First: Suppose we know what the jobs are that need to be executed, and how long each job will take ⇒ optimize the turnaround times per job. Turnaround: delay between job submission and job completion. turnaround time 8 8 4 4 8 4 12 16 20 4 4 4 12 4 20 average 14 11 8 Note: Scheduling the shortest job first leads to minimal average turnaround time per job. 02 – 35 Processes/2.4 Process Scheduling Batch systems (2/2) Problems: 1: You have to know all jobs in advance ⇒ cheat a bit: shortest jobs get queued at the head, but running jobs are never preempted. (there’s a problem here – what is it?) However, you will not get an optimum. 2: You have to know the completion time per job ⇒ use estimates based on previous runs, e.g. Tk+1 = a · Tk−1 + (1 − a) · Tk Question: What happens if a = 0 or a = 1? 02 – 36 Processes/2.4 Process Scheduling Interactive systems Problem: we don’t want some process to run to completion if that’s going to take a very long time. Instead, the process should be interrupted and suspended in favor of some other process ⇒ preemptive scheduling. select preempt CPU Note: what we need is some way to switch between processes ⇒ context switching. 02 – 37 Processes/2.4 Process Scheduling Context switching Problem: We have to change from one process to another. The stuff that is going to be used by another process should be saved ⇒ CPU registers. P scheduler P* idle executing save registers select next process restore registers save registers select next process restore registers 02 – 38 Processes/2.4 Process Scheduling Round-robin scheduling CPU Simple idea: every process is assigned a quantum – a number of time units that it is allowed to use the CPU without interruption. If the process blocks, or the quantum is exceeded, the scheduler assigns the CPU to the next process in line. Question: How big should a quantum be? 02 – 39 Processes/2.4 Process Scheduling Priority scheduling Idea: Combine round-robin with process priorities: a process with a higher priority is selected first. Queue headers Runable processes Priority 4 (Highest priority) Priority 3 Priority 2 Priority 1 (Lowest priority) Issues: • Are priorities assigned statically or dynamically? • Can priorities be adjusted during execution? • How are priorities assigned? 02 – 40 Processes/2.4 Process Scheduling Process management in MINIX Layer Init 4 3 Process manager 2 Disk driver 1 User process File system TTY driver Kernel User process Info server User process Network server Ethernet driver … … Server processes User mode Device drivers … Clock task User processes System task Kernel Kernel mode • All communication at layers 2–4 through messagepassing. • Layers 1–2, and often 1–3 in other systems are usually taken together into a single binary to be executed in kernel mode. The rest executes in user mode. MINIX 3 has a microkernel. Question: What’s so good about having the file system (and process manager), as well as all the drivers, outside the kernel? 02 – 41 Processes/2.5 Processes in MINIX IPC in MINIX Essence: All interprocess communication uses blocking message passing: • When a process sends a message, it is blocked until the receiver actually reads it. • A process is always blocked when receiving a message until there is something to read. Processes can communicate only with processes in their own layer, and with the layer just below them. MINIX uses priority scheduling, with one scheduling queue per priority. User processes have the lowest priority; device driver tasks the highest. Important: MINIX runs as several totally independent programs (minimum: kernel, drivers, process manager, file system). This means that procedures with the same name, but in different programs, do not conflict. 02 – 42 Processes/2.5 Processes in MINIX Memory layout Limit of memory Memory available for user programs src/servers/init/init 3549K Init src/drivers/at_wini/at_wini Disk driver src/drivers/log/log Log driver src/drivers/memory/memory Memory driver src/drivers/tty/tty Console driver src/servers/rs/rs Reincarnation server src/servers/fs/fs File system src/servers/pm/pm Process manager 3537K 3489K 3416K 3403K 3375K 3236K (Depends on number of buffers included in file system) 1093K 1024K Read only memory and I/O adapter memory (unavailable to MINIX 3) [Boot monitor] 640K 590K Memory available for user programs 55K System task src/kernel/kernel Clock task Kernel 2K Start of kernel [Used by BIOS] [Interrupt vectors] 02 – 43 1K 0 Processes/2.5 Processes in MINIX C Include File Semantics In C, files can be merged by #include statements: file A #ifndef A_H #define A_H code in file A #endif #ifndef A_H #define A_H file B code in file A #include A #endif code file B #ifndef A_H #define A_H code in file A This part is skipped by the compiler #endif file C #include A #include B code file B code file C code file C 02 – 44 Processes/2.6 Implementation in MINIX C Scope Semantics Modules in C are modeled by source files. Types, variables, etc., can be exported to other files (default); imported (#extern), or be explicitly restricted to the current file (#static): #define PUBLIC #define EXTERN extern #define PRIVATE static EXTERN int k; PRIVATE int n; EXTERN int k; PRIVATE int n; PUBLIC int k; PRIVATE int n; 02 – 45 Processes/2.6 Implementation in MINIX Messages (1/2) In MINIX, there are seven different message types: m_source m_source m_source m_source m_source m_source m_source m_type m_type m_type m_type m_type m_type m_type m1_i1 m2_i1 m3_i1 m4_l1 m7_i1 m8_i1 m7_i2 m8_i2 m7_i3 m8_p1 m7_i4 m8_p2 m7_p1 m8_p3 m7_p2 m8_p4 m5_c2 m5_c1 m5_i1 m1_i2 m2_i2 m3_i2 m4_l2 m5_i2 m1_i3 m2_i3 m3_p1 m4_l3 m5_l1 m1_p1 m2_l1 m4_l4 m5_l2 m1_p2 m2_l2 m4_l5 m3_ca1 m5_l3 m1_p3 m2_p1 int: i char*: p long: l char: c char[]: ca 02 – 46 Processes/2.6 Implementation in MINIX Messages – Dereferencing 03020 03021 03022 03023 03024 03025 03026 03027 03028 03029 03030 03031 03032 03033 03034 03035 03036 03037 03038 03039 03040 ..... 03049 03050 03051 03052 typedef struct { int m_source; int m_type; union { mess_1 m_m1; mess_2 m_m2; mess_3 m_m3; mess_4 m_m4; mess_5 m_m5; mess_7 m_m7; mess_8 m_m8; } m_u; } message; /* who sent the message */ /* what kind of message is it */ /* The following defines provide names for useful members. */ #define m1_i1 m_u.m_m1.m1i1 #define m1_i2 m_u.m_m1.m1i2 #define m1_i3 m_u.m_m1.m1i3 #define m1_p1 m_u.m_m1.m1p1 #define m1_p2 m_u.m_m1.m1p2 #define m1_p3 m_u.m_m1.m1p3 #define #define #define #define m3_i1 m3_i2 m3_p1 m3_ca1 m_u.m_m3.m3i1 m_u.m_m3.m3i2 m_u.m_m3.m3p1 m_u.m_m3.m3ca1 m1 i3 ⇒ msg type #1, int-field #3 m3 ca1 ⇒ msg type #3, character-array-field #1 message msg; msg.m_u.m_m1.m1i3 = 3; msg.m1_i3 = 3; 02 – 47 Processes/2.6 Implementation in MINIX System Calls (1/2) Remember: Whenever a system call is made, the call is translated into a message to a specific structure. 3670 /*===========================================================================* 03671 * Messages for BLOCK and CHARACTER device drivers * 03672 *===========================================================================*/ 03673 03674 /* Message types for device drivers. */ 03675 #define DEV_RQ_BASE 0x400 /* base for device request types */ 03676 #define DEV_RS_BASE 0x500 /* base for device response types */ 03677 03678 #define CANCEL (DEV_RQ_BASE + 0) /* general req to force a task to cancel */ 03679 #define DEV_READ (DEV_RQ_BASE + 3) /* read from minor device */ 03680 #define DEV_WRITE (DEV_RQ_BASE + 4) /* write to minor device */ 03681 #define DEV_IOCTL (DEV_RQ_BASE + 5) /* I/O control code */ 03682 #define DEV_OPEN (DEV_RQ_BASE + 6) /* open a minor device */ 03683 #define DEV_CLOSE (DEV_RQ_BASE + 7) /* close a minor device */ 03684 #define DEV_SCATTER (DEV_RQ_BASE + 8) /* write from a vector */ 03685 #define DEV_GATHER (DEV_RQ_BASE + 9) /* read into a vector */ 03686 #define TTY_SETPGRP (DEV_RQ_BASE + 10) /* set process group */ 03687 #define TTY_EXIT (DEV_RQ_BASE + 11) /* process group leader exited */ 03688 #define DEV_SELECT (DEV_RQ_BASE + 12) /* request select() attention */ 03689 #define DEV_STATUS (DEV_RQ_BASE + 13) /* request driver status */ 03690 03691 #define DEV_REPLY (DEV_RS_BASE + 0) /* general task reply */ 03692 #define DEV_CLONED (DEV_RS_BASE + 1) /* return cloned minor */ 03693 #define DEV_REVIVE (DEV_RS_BASE + 2) /* driver revives process */ 03694 #define DEV_IO_READY (DEV_RS_BASE + 3) /* selected device ready */ 03695 #define DEV_NO_STATUS (DEV_RS_BASE + 4) /* empty status reply */ 03696 03697 /* Field names for messages to block and character device drivers. */ 03698 #define DEVICE m2_i1 /* major-minor device */ m2_i2 /* which (proc) wants I/O? */ 03699 #define PROC_NR 03700 #define COUNT m2_i3 /* how many bytes to transfer */ 03701 #define REQUEST m2_i3 /* ioctl request code */ 03702 #define POSITION m2_l1 /* file offset */ 03703 #define ADDRESS m2_p1 /* core buffer address */ 11215 11216 02 – 48 /* Transfer bytes from/to the device. */ r = (*dp->dr_transfer)(mp->PROC_NR, mp->m_type, mp->POSITION, iov, nr_req); Processes/2.6 Implementation in MINIX System Calls (2/2) 05409 05410 ..... 05413 05414 05415 05416 05417 05418 05419 #define SYSCALL_FUNC #define SYSCALL_FLAGS 07480 07481 07482 07483 07484 07485 07486 07487 07488 07489 07490 07491 ..... 07558 07559 07560 07561 07562 07563 07564 07565 07566 07567 07568 07569 07570 07571 07572 ..... 07580 07581 07582 07583 07584 07585 07586 PUBLIC int sys_call(call_nr, src_dst, m_ptr) int call_nr; /* system call number and flags */ int src_dst; /* src to receive from or dst to send to */ message *m_ptr; /* pointer to message in the caller’s space */ { /* System calls are done by trapping to the kernel with an INT instruction. * The trap is caught and sys_call() is called to send or receive a message * (or both). The caller is always given by ’proc_ptr’. */ register struct proc *caller_ptr = proc_ptr; /* get pointer to caller */ int function = call_nr & SYSCALL_FUNC; /* get system call function */ unsigned flags = call_nr & SYSCALL_FLAGS; /* get flags */ 0x0F 0xF0 /* mask for system call function */ /* mask for system call flags */ /* System call numbers that are passed when trapping to the kernel. The * numbers are carefully defined so that it can easily be seen (based on * the bits that are on) which checks should be done in sys_call(). */ #define SEND 1 /* 0 0 0 1 : blocking send */ #define RECEIVE 2 /* 0 0 1 0 : blocking receive */ #define SENDREC 3 /* 0 0 1 1 : SEND + RECEIVE */ switch(function) { case SENDREC: /* A flag is set so that notifications cannot interrupt SENDREC. */ priv(caller_ptr)->s_flags |= SENDREC_BUSY; /* fall through */ case SEND: result = mini_send(caller_ptr, src_dst, m_ptr, flags); if (function == SEND || result != OK) { break; /* done, or SEND failed */ } /* fall through for SENDREC */ case RECEIVE: if (function == RECEIVE) priv(caller_ptr)->s_flags &= ~SENDREC_BUSY; result = mini_receive(caller_ptr, src_dst, m_ptr, flags); break; default: result = EBADCALL; } /* illegal system call */ /* Now, return the result of the system call to the caller. */ return(result); } 02 – 49 Processes/2.6 Implementation in MINIX Process Table 5500 #ifndef PROC_H 05501 #define PROC_H 05502 05503 /* Here is the declaration of the process table. It contains all process 05504 * data, including registers, flags, scheduling priority, memory map, 05505 * accounting, message passing (IPC) information, and so on. 05506 * ..... 05510 */ ..... 05516 struct proc { 05517 struct stackframe_s p_reg; /* process’ registers saved in stack frame */ 05518 reg_t p_ldt_sel; /* selector in gdt with ldt base and limit */ 05519 struct segdesc_s p_ldt[2+NR_REMOTE_SEGS]; /* CS, DS and remote segments */ 05520 05521 proc_nr_t p_nr; /* number of this process (for fast access) */ 05522 struct priv *p_priv; /* system privileges structure */ 05523 char p_rts_flags; /* SENDING, RECEIVING, etc. */ 05524 05525 char p_priority; /* current scheduling priority */ 05526 char p_max_priority; /* maximum scheduling priority */ 05527 char p_ticks_left; /* number of scheduling ticks left */ 05528 char p_quantum_size; /* quantum size in ticks */ 05529 05530 struct mem_map p_memmap[NR_LOCAL_SEGS]; /* memory map (T, D, S) */ 05531 05532 clock_t p_user_time; /* user time in ticks */ 05533 clock_t p_sys_time; /* sys time in ticks */ 05534 05535 struct proc *p_nextready; /* pointer to next ready process */ 05536 struct proc *p_caller_q; /* head of list of procs wishing to send */ 05537 struct proc *p_q_link; /* link to next proc wishing to send */ 05538 message *p_messbuf; /* pointer to passed message buffer */ 05539 proc_nr_t p_getfrom; /* from whom does process want to receive? */ 05540 proc_nr_t p_sendto; /* to whom does process want to send? */ 05541 05542 sigset_t p_pending; /* bit map for pending kernel signals */ 05543 05544 char p_name[P_NAME_LEN]; /* name of the process, including \0 */ 05545 }; 02 – 50 Processes/2.6 Implementation in MINIX MINIX v3 processes (1/2) Note: Not all processes are equal, and we need a way to distinguish their privileges: 05718 struct priv { 05719 proc_nr_t s_proc_nr; /* number of associated process */ 05720 sys_id_t s_id; /* index of this system structure */ 05721 short s_flags; /* PREEMTIBLE, BILLABLE, etc. */ 05722 05723 short s_trap_mask; /* allowed system call traps */ 05724 sys_map_t s_ipc_from; /* allowed callers to receive from */ 05725 sys_map_t s_ipc_to; /* allowed destination processes */ 05726 long s_call_mask; /* allowed kernel calls */ 05727 05728 sys_map_t s_notify_pending; /* bit map with pending notifications */ 05729 irq_id_t s_int_pending; /* pending hardware interrupts */ 05730 sigset_t s_sig_pending; /* pending signals */ 05731 05732 timer_t s_alarm_timer; /* synchronous alarm timer */ 05733 struct far_mem s_farmem[NR_REMOTE_SEGS]; /* remote memory map */ 05734 reg_t *s_stack_guard; /* stack guard word for kernel tasks */ 05735 }; Important: Only low-level user-mode device drivers are allowed to, for example, request register manipulations. 02 – 51 Processes/2.6 Implementation in MINIX MINIX v3 processes (2/2) -nr(-4) [-3] [-2] [-1] 0 1 2 3 ... 7 ... 15 -id-name---(01) IDLE (02) CLOCK (03) SYSTEM (04) KERNEL (05) pm (06) fs (07) rs (09) memory (00) init -flags- -trapsP-BS-------S--R----S--R----S----P--SESRBN P--SESRBN P--SESRBN P--SESRBN -ipc_to mask----------------------00000000 00001111 10000000 0000000 00000000 00001111 10000000 0000000 00000000 00001111 10000000 0000000 00000000 00001111 10000000 0000000 11111111 11111111 10000000 0000000 11111111 11111111 10000000 0000000 11111111 11111111 10000000 0000000 00110111 01101111 10000000 0000000 P-B-- E--B- 00000111 00000000 00000000 0000000 (16) printer P--S- ESRBN 01111111 11111111 11111111 1111111 Note: The debugger provides info on who can do what. We see that the init process can call only pm, fs, and rs by means of B = send/receive calls (i.e., synchronous communication. VERY IMPORTANT: CHECK THIS OUT FOR YOURSELF WITH THE MINIX CD 02 – 52 Processes/2.6 Implementation in MINIX Bootstrapping MINIX (1/2) Boot pr ogr am boot record & s te r Ma n 1 bootbloc part itio o k r t it i n Pa loa d n 2 boot s rtitio blo Pa loads c b ta ock tbl s o Bo load le rogram for pa rtit io rogram for ot p Bo ot p Bo k n 1 on 2 rtiti pa (a) (b) Basic idea: A computer has a ROM-installed program that automatically reads the first 512 bytes of the bootblock into memory. The loaded code is then executed (program Boot). 02 – 53 Processes/2.6 Implementation in MINIX Bootstrapping MINIX (2/2) • Boot reads the second sector of the (1K) bootblock, and inspects the parameters saved there. • The user is presented with options for loading the rest of the system, or can even choose to load other OSs that lie in different partitions. • Boot normally simply loads the binary image of a compiled MINIX version into main memory, and jumps to main(). • main()’s task is to initialize memory and the process table (there are some processes running now!), and start the shell at the console. Question: We can use a very similar scheme for diskless workstations. How? 02 – 54 Processes/2.6 Implementation in MINIX Interrupt Handling (1/2) Interrupt INT CPU s y s t e m IRQ 0 (clock) IRQ 1 (keyboard) INT Master interrupt controller IRQ 3 (tty 2) IRQ 4 (tty 1) IRQ 5 (XT Winchester) IRQ 6 (floppy) IRQ 7 (printer) ACK INTA Interrupt ack d a t a b u s INT Slave interrupt controller ACK IRQ 8 (real time clock) IRQ 9 (redirected IRQ 2) IRQ 10 IRQ 11 IRQ 12 IRQ 13 (FPU exception) IRQ 14 (AT Winchester) IRQ 15 During system initialization, main() initializes the controllers and sets the interrupt vector. 02 – 55 Processes/2.6 Implementation in MINIX Interrupt Handling (2/2) 1: Save registers of interrupted process 2: Handle the interrupt 3: Continue by rescheduling the best process. Note: The hardware already saves a number of key registers by pushing them onto a new stack of the interrupted process. 06515 06516 06517 06518 06519 06520 06521 06522 06523 06524 06525 06526 06527 02 – 56 #define hwint_master(irq) \ call save push (_irq_handlers+4*irq) call _intr_handle pop ecx cmp (_irq_actids+4*irq), 0 jz 0f inb INT_CTLMASK orb al, [1<<irq] outb INT_CTLMASK 0: movb al, END_OF_INT outb INT_CTL ret /* save interrupted process state */;\ /* irq_handlers[irq] */;\ /* intr_handle(irq_handlers[irq]) */;\ ;\ /* interrupt still active? */;\ ;\ /* get current mask */ ;\ /* mask irq */ ;\ /* disable the irq */;\ ;\ /* reenable master 8259 */;\ /* restart (another) process */ Processes/2.6 Implementation in MINIX Saving registers 06617 06618 06619 06620 06621 06622 06623 06624 06625 06626 06627 06628 06629 06630 06631 06632 06633 06634 06635 06636 06637 06638 06639 06640 06641 06642 06643 02 – 57 ! Save for protected mode. ! This is much simpler than for 8086 mode, because the stack already points ! into the process table, or has already been switched to the kernel stack. .align 16 cld pushad push push push push mov mov mov mov incb jnz mov push xor jmp ! set direction flag to a known value ! save "general" registers ds ! save ds es ! save es fs ! save fs gs ! save gs dx, ss ! ss is kernel data segment ds, dx ! load rest of kernel segments es, dx ! kernel does not use fs, gs eax, esp ! prepare to return (_k_reenter) ! from -1 if not reentering set_restart1 ! stack is already kernel stack esp, k_stktop ! "! $#&% '()$% _restart ! build return address for int handler ebp, ebp ! for stacktrace RETADR-P_STACKBASE(eax) ! * $#! ,+$#-./,0$#1.2 save: o16 o16 o16 o16 .align set_restart1: push jmp 4 restart1 RETADR-P_STACKBASE(eax) ! * $#! ,+$#-./,0$#1.2 Processes/2.6 Implementation in MINIX Restoring registers 06681 06682 06683 06684 06685 06686 06687 06688 06689 06690 06691 06692 06693 06694 06695 06696 06697 06698 06699 06700 06701 06702 02 – 58 _restart: ! Restart the current process or the next process if it is set. cmp jz mov mov mov 0: mov lldt lea mov restart1: decb o16 pop o16 pop o16 pop o16 pop popad add iretd (_next_ptr), 0 0f eax, (_next_ptr) (_proc_ptr), eax (_next_ptr), 0 esp, (_proc_ptr) P_LDT_SEL(esp) eax, P_STACKTOP(esp) (_tss+TSS3_S_SP0), eax ! see if another process is scheduled ! schedule new process ! ! ! ! will assume P_STACKBASE == 0 enable process’ segment descriptors arrange for next interrupt to save state in process table (_k_reenter) gs fs es ds esp, 4 ! skip return adr ! '0/ ! continue process Processes/2.6 Implementation in MINIX $# System Calls 06648 06649 06650 06651 06652 06653 06654 06655 06656 06657 06658 06659 06660 06661 06662 06663 06664 06665 06666 06667 06668 06669 06670 06671 06672 06673 06674 06675 06676 .align _s_call: _p_s_call: cld sub push push push o16 push o16 push o16 push o16 push mov mov mov incb mov mov xor push push push call mov 16 esp, 6*4 ebp esi edi ds es fs gs dx, ss ds, dx es, dx (_k_reenter) esi, esp esp, k_stktop ebp, ebp ! set direction flag to a known value ! skip RETADR, eax, ecx, edx, ebx, est ! stack already points into proc table ! ! ! ! ! ebx ! eax ! ecx ! _sys_call ! ! AXREG(esi), eax ! assumes P_STACKBASE == 0 "! $#&% '()$% for stacktrace end of inline save now set up parameters for sys_call() pointer to user message src/dest SEND/RECEIVE/BOTH sys_call(function, src_dest, m_ptr) caller is now explicitly in proc_ptr sys_call MUST PRESERVE si ! Fall into code to restart proc/task running. Note: s call is called by a nonkernel process to invoke a system service. This means switching from user to kernel mode. When call is finished, we continue with restart(). 02 – 59 Processes/2.6 Implementation in MINIX Interprocess communication Model: assume a process P sends a message msg to process Q. 1: P calls send(P, Q, msg). 2: If Q is prepared to receive a message from (1) P, or (2) any process, msg is copied from P to Q’s buffer space. Both P and Q can continue. 3: If Q cannot receive a message from P, P is blocked and queued until its message can be delivered. Assume Q wants to receive a message: 1: First check if there is a process that is blocking because it wanted to send a message to Q. 2: If there was a message already pending, the message is copied to Q’s buffer space, and sender and receiver continue. 3: If there was no message pending, Q blocks until one does. 02 – 60 Processes/2.6 Implementation in MINIX Example: Doing a System Call 07480 07481 07482 07483 07484 07485 07486 07487 07488 07489 07490 07491 ..... 07558 07559 07560 07561 07562 07563 07564 07565 07566 07567 07568 07569 07570 07571 07572 ..... 07580 07581 07582 07583 07584 07585 07586 PUBLIC int sys_call(call_nr, src_dst, m_ptr) int call_nr; /* system call number and flags */ int src_dst; /* src to receive from or dst to send to */ message *m_ptr; /* pointer to message in the caller’s space */ { /* System calls are done by trapping to the kernel with an INT instruction. * The trap is caught and sys_call() is called to send or receive a message * (or both). The caller is always given by ’proc_ptr’. */ register struct proc *caller_ptr = proc_ptr; /* get pointer to caller */ int function = call_nr & SYSCALL_FUNC; /* get system call function */ unsigned flags = call_nr & SYSCALL_FLAGS; /* get flags */ switch(function) { case SENDREC: /* A flag is set so that notifications cannot interrupt SENDREC. */ priv(caller_ptr)->s_flags |= SENDREC_BUSY; /* fall through */ case SEND: result = mini_send(caller_ptr, src_dst, m_ptr, flags); if (function == SEND || result != OK) { break; /* done, or SEND failed */ } /* fall through for SENDREC */ case RECEIVE: if (function == RECEIVE) priv(caller_ptr)->s_flags &= ~SENDREC_BUSY; result = mini_receive(caller_ptr, src_dst, m_ptr, flags); break; default: result = EBADCALL; } } /* illegal system call */ /* Now, return the result of the system call to the caller. */ return(result); Note: we’re sending (7564) a message to the process that handles the call, and block until we receive the answer (7571). 02 – 61 Processes/2.6 Implementation in MINIX Sending a Message 07591 07592 07593 07594 07595 07596 07597 07598 07599 07600 ..... 07605 07606 07607 07608 07609 07610 07611 07612 07613 07614 07615 07616 07617 07618 07620 07621 07622 07623 07624 07625 07626 07627 07628 07629 07630 07631 07632 07633 07634 07635 07636 07637 PRIVATE int mini_send(caller_ptr, dst, m_ptr, flags) register struct proc *caller_ptr; /* who is trying to send a message? */ int dst; /* to whom is message being sent? */ message *m_ptr; /* pointer to message buffer */ unsigned flags; /* system call flags */ { /* Send a message from ’caller_ptr’ to ’dst’. If ’dst’ is blocked waiting * for this message, copy the message to it and unblock ’dst’. If ’dst’ is * not waiting at all, or is waiting for another source, queue ’caller_ptr’. */ /* Check for deadlock by ’caller_ptr’ and ’dst’ sending to each other. */ xp = dst_ptr; while (xp->p_rts_flags & SENDING) { /* check while sending */ xp = proc_addr(xp->p_sendto); /* get xp’s destination */ if (xp == caller_ptr) return(ELOCKED); /* deadlock if cyclic */ } /* Check if ’dst’ is blocked waiting for this message. The destination’s * SENDING flag may be set when its SENDREC call blocked while sending. */ if ( (dst_ptr->p_rts_flags & (RECEIVING | SENDING)) == RECEIVING && (dst_ptr->p_getfrom == ANY || dst_ptr->p_getfrom == caller_ptr->p_nr)) { /* Destination is indeed waiting for this message. */ CopyMess(caller_ptr->p_nr, caller_ptr, m_ptr, dst_ptr, dst_ptr->p_messbuf); if ((dst_ptr->p_rts_flags &= ~RECEIVING) == 0) enqueue(dst_ptr); } else if ( ! (flags & NON_BLOCKING)) { /* Destination is not waiting. Block and dequeue caller. */ caller_ptr->p_messbuf = m_ptr; if (caller_ptr->p_rts_flags == 0) dequeue(caller_ptr); caller_ptr->p_rts_flags |= SENDING; caller_ptr->p_sendto = dst; } 02 – 62 /* Process is now blocked. Put in on the destination’s queue. */ xpp = &dst_ptr->p_caller_q; /* find end of list */ while (*xpp != NIL_PROC) xpp = &(*xpp)->p_q_link; *xpp = caller_ptr; /* add caller to end */ caller_ptr->p_q_link = NIL_PROC; /* mark new end of list */ } else { return(ENOTREADY); } return(OK); !$ # # ! Processes/2.6 Implementation in MINIX Receiving a Message 07642 07643 07644 07645 07646 07647 07648 07649 07650 07651 ..... 07660 07661 07662 07663 07664 ..... 07688 07689 07690 07691 07692 07693 07694 07695 07696 07697 07698 07699 07700 07701 07702 07703 07704 07705 07706 07707 07708 07709 07710 07711 07712 07713 07714 PRIVATE int mini_receive(caller_ptr, src, m_ptr, flags) register struct proc *caller_ptr; /* process trying to get message */ int src; /* which message source is wanted */ message *m_ptr; /* pointer to message buffer */ unsigned flags; /* system call flags */ { /* A process or task wants to get a message. If a message is already queued, * acquire it and deblock the sender. If no message from the desired source * is available block the caller, unless the flags don’t allow blocking. */ /* Check to see if a message from desired source is already available. * The caller’s SENDING flag may be set if SENDREC couldn’t send. If it is * set, the process should be blocked. */ if (!(caller_ptr->p_rts_flags & SENDING)) { } } 02 – 63 /* Check caller queue. Use pointer pointers to keep code simple. */ xpp = &caller_ptr->p_caller_q; while (*xpp != NIL_PROC) { if (src == ANY || src == proc_nr(*xpp)) { /* Found acceptable message. Copy it and update status. */ CopyMess((*xpp)->p_nr, *xpp, (*xpp)->p_messbuf, caller_ptr, m_ptr); if (((*xpp)->p_rts_flags &= ~SENDING) == 0) enqueue(*xpp); *xpp = (*xpp)->p_q_link; /* remove from queue */ return(OK); /* report success */ } xpp = &(*xpp)->p_q_link; /* proceed to next */ } /* No suitable message is available or the caller couldn’t send in SENDREC. * Block the process trying to receive, unless the flags tell otherwise. */ if ( ! (flags & NON_BLOCKING)) { caller_ptr->p_getfrom = src; caller_ptr->p_messbuf = m_ptr; if (caller_ptr->p_rts_flags == 0) dequeue(caller_ptr); caller_ptr->p_rts_flags |= RECEIVING; return(OK); } else { return(ENOTREADY); } Processes/2.6 Implementation in MINIX Scheduling rdy_head 15 IDLE_Q rdy_tail IDLE_Q IDLE . . . 7 USER_Q . . . USER_Q init . . . fs 3 rs pm 2 disk log 1 tty • TASK_Q 7 . . . 4 0 15 system 4 3 mem 2 1 clock TASK_Q 0 : select the most suitable task/process to run, starting with highest priority queue. • : enter a task/process into the appropriate queue. • : do the opposite. • : reshuffle the USER Q when a quantum has expired: the head is moved to the end. 02 – 64 Processes/2.6 Implementation in MINIX System Task (1/2) Problem: because the memory manager, file system, drivers and other typical OS processes are placed outside the kernel, they cannot modify kernel data structures. However, they do control a lot of things that are administrated in the kernel. Solution: install a separate system task that (1) communicates with these special processes, (2) operates in kernel mode, (3) changes kernel data structures on behalf of these processes. 02 – 65 Processes/2.7 System Task System Task (2/2) 02 – 66 Processes/2.7 System Task Clock – Hardware Essentially just two types: • Simple ones that generate an interrupt with each cycle of the power supply (every 20 or 16.7 ms) • Advanced ones with their own oscillator by which a counter is decremented. Whenever the counter hits zero, an interrupt is generated. The advanced ones are really what we need because you can program them. Example: if the oscillator has a frequency of 1 MHz, and the clock is connected to a 16-bit register, we can set the timer between 1 and 65536 µs. Crystal oscillator Counter is decremented at each pulse Holding register is used to load the counter 02 – 67 Processes/2.8 Clocks Clock – Software Basic functions: • Maintaining time of day (just count ticks in software). • Lowering the quantum of a running process (decrement a software counter). • Accounting for CPU usage (guess how...) • Handling alarms and watchdogs. • Profiling, monitoring, and statistics gathering. Current time Next signal 4200 3 Clock header 3 02 – 68 4 6 2 1 X Processes/2.8 Clocks The Clock Task 10468 10469 10470 10471 10472 10473 10474 10475 10476 10477 10478 10479 10480 10481 10482 10483 10484 10485 10486 10487 10488 10489 10490 10491 10492 PUBLIC void clock_task() { /* Main program of clock task. If the call is not HARD_INT it is an error. */ message m; /* message buffer for both input and output */ int result; /* result returned by the handler */ init_clock(); /* initialize clock task */ /* Main loop of the clock task. while (TRUE) { Get work, process it. Never reply. */ /* Go get a message. */ receive(ANY, &m); } 02 – 69 } /* Handle the request. Only clock ticks are expected. */ switch (m.m_type) { case HARD_INT: result = do_clocktick(&m); /* handle clock tick */ break; default: /* illegal request type */ kprintf("CLOCK: illegal request %d from %d.\n", m.m_type,m.m_source); } Processes/2.8 Clocks Processing a clock tick 10497 10498 10499 10500 10501 10502 10503 10504 10505 10506 10507 10508 10509 10510 10511 10512 10513 10514 10515 10516 10517 10518 10519 10520 10521 10522 10523 10524 PRIVATE int do_clocktick(m_ptr) message *m_ptr; /* pointer to request message */ { /* Despite its name, this routine is not called on every clock tick. It * is called on those clock ticks when a lot of work needs to be done. */ /* A process used up a full quantum. The interrupt handler stored this * process in ’prev_ptr’. First make sure that the process is not on the * scheduling queues. Then announce the process ready again. Since it has * no more time left, it gets a new quantum and is inserted at the right * place in the queues. As a side-effect a new process will be scheduled. */ if (prev_ptr->p_ticks_left <= 0 && priv(prev_ptr)->s_flags & PREEMPTIBLE) { lock_dequeue(prev_ptr); /* take it off the queues */ lock_enqueue(prev_ptr); /* and reinsert it again */ } /* Check if a clock timer expired and run its watchdog function. */ if (next_timeout <= realtime) { tmrs_exptimers(&clock_timers, realtime, NULL); next_timeout = clock_timers == NULL ? TMR_NEVER : clock_timers->tmr_exp_time; } /* Inhibit sending a reply. */ return(EDONTREPLY); } 02 – 70 Processes/2.8 Clocks Clock interrupt handler 10556 10557 10558 10559 10560 10561 ..... 10583 10584 10585 10586 10587 10588 10589 10590 10591 10592 10593 10594 10595 10596 10597 10598 10599 10600 10601 10602 10603 10604 10605 10606 10607 10608 10609 10610 10611 10612 10613 10614 10615 PRIVATE int clock_handler(hook) irq_hook_t *hook; { /* This executes on each clock tick (i.e., every time the timer chip generates * an interrupt). It does a little bit of work so the clock task does not have * to be called on every tick. */ register unsigned ticks; /* Acknowledge the PS/2 clock interrupt. */ if (machine.ps_mca) outb(PORT_B, inb(PORT_B) | CLOCK_ACK_BIT); /* Get number of ticks and update realtime. */ ticks = lost_ticks + 1; lost_ticks = 0; realtime += ticks; /* Update user and system accounting times. Charge the current process for * user time. If the current process is not billable, that is, if a non-user * process is running, charge the billable process for system time as well. * Thus the unbillable process’ user time is the billable user’s system time. */ proc_ptr->p_user_time += ticks; if (priv(proc_ptr)->s_flags & PREEMPTIBLE) { proc_ptr->p_ticks_left -= ticks; } if (! (priv(proc_ptr)->s_flags & BILLABLE)) { bill_ptr->p_sys_time += ticks; bill_ptr->p_ticks_left -= ticks; } /* Check if do_clocktick() must be called. Done for alarms and scheduling. * Some processes, such as the kernel tasks, cannot be preempted. */ if ((next_timeout <= realtime) || (proc_ptr->p_ticks_left <= 0)) { prev_ptr = proc_ptr; /* store running process */ lock_notify(HARDWARE, CLOCK); /* send notification */ } return(1); /* reenable interrupts */ } 02 – 71 Processes/2.8 Clocks