440 likes | 654 Views
Linux Processes. CNS 4510. Processes. When created is almost identical to parent process Copy of parent’s address space Executes same code as parent Separate copies of stack and heap. Threads. Modern Unix systems support multi-threaded applications
E N D
Linux Processes CNS 4510
Processes • When created is almost identical to parent process • Copy of parent’s address space • Executes same code as parent • Separate copies of stack and heap
Threads • Modern Unix systems support multi-threaded applications • In modern Unix systems a process is a collection of threads • Each thread represents an execution flow of the process • Most multi-threaded application are written using Posix threads
Threads • Older versions of the Linux kernel offered no support for multithreaded applications. • From the kernel point of view a multi threaded application was simply a process • Threads were just in user space
Threads • Consider a chess program • One controlling a graphical chess boardAnd waiting for user input • Another computing the computer’s nextmove • If the chess program is just one process, the first thread cannot simply issue a blocking system call • Why?
Lightweight Processes • Linux uses lightweight processes to offer better support for multithreaded applications • Two lightweight processes may share some resources • A good way to implement multithreaded applications is to associate a lightweight process to each thread • Two examples of POSIX-compliant pthread libraries • LinuxThreads • IBM’s Next Generation Posix Threading Package (NGPT)
Process Descriptor • Stores needed information for each process • Implemented through the task_struct structure
Process State • There are 5 states a process can be in • Task_Running • Task_Interruptible • Sleeping until some condition is true • Task_uninterruptible • device driver probing for a certain hardware state • Signals to this process leaves it unchanged • Task_Stopped • Task_Zombie • Kernel uses set_task_state and set_current_state macros to set state.
Process Identification • Each process has a unique pid • However, most of the time a process is referred to by it’s PDP • (Process Descriptor pointer) • Max Pid is 32,767
Thread Groups • Unix programmers expect threads in the same group to have a common PID • Linux 2.4 has the notion of a thread group • All descriptors in the same group are collected in a linked list implemented through the thread_group field of the task_struct • The shared PID is the PID for the first thread. It is stored in the tgid field • getpid( ) returns current->tgid if process is part of a thread group
Handling process descriptors • Linux stores two different data structures in a single 8kb memory area • Process Kernel mode stack • Process Descriptor • esp register • CPU stack pointer • stack grows up (towards low memory)
Task Union union task_union { struct task_struct task; unsigned long stack[2048]; };
The current macro • Can obtain the process descriptor pointer from the value of the esp register • The current macro masks out the 13 least significant bits of esp to obtain the address of the process descriptor • Don’t need a separate current process pointer
The process list • Kernel has several lists of process • contain pdp’s • The prev and next pointers are part of the process descriptor’s structure • Process list is a circular doubly linked list. • process 0 (or swapper) is the head of the list #define for_each_task(p) for (p=&init_task;(p=p->next_task)!=&init_task;)
The Run Queue • Kernel also contains a list of TASK_RUNNING processes • the RUN_LIST variable contains the head and tail of this List. • Convenient functions • add_to_runqueue() • delete_from_runqueue() • move_first_runqueue() • move_last_runqueue() • What should be included in the Wake_up_process() procedure
PID Hash table • Kernel also includes a hash table • pid -> pdp • Chaining is used to handle collisions • Better than an array • why?
Parenthood Relationships • Process descriptor includes the following fields • p_opptr (Original parent) • points to 1 if parent no longer exists • p_pptr (parent) • points to the current parent • p_cptr(child) • points to youngest child • p_ysptr(younger sibling) • points to pdp of process created immediately after p by p’s current parent • p_osptr (older sibling)
Process Organization • TASK_RUNNING • has its own list • TASK_STOPPED, TASK_ZOMBIE • no special structure • TASK_INTERRUPTIBLE, TASK_UNINTERRUPTIBLE • subdivided into many classes each of which corresponds to a specific event. • wait queues
Wait Queues • Used for • interrupt handling • process synchronization • timing • Each wait-queue head includes a lock for the specific resource corresponding to the particular queue • Only wake up one process in order to avoid the “thundering herd” problem
Handling wait queues • Useful functions • add_wait_queue( ) • add_wait_queue_exclusive( ) • remove_wait_queue() • wait_queue_active() • is this queue empty? • DELCLARE_WAIT_QUEUE_HEAD(name) • new wait queue
Wait_queue_t struct __wait_queue{ unsigned int flags; struct task_struct * task; struct list_head task_list; }; typedef struct __wait_queue wait_queue_t; Elements of a wait queue list are of type wait_queue_t Each element in the wait queue list represents a sleeping process.
__wait_queue_head struct __wait_queue_head { spinlock_t lock; struct list_head task_list; }; typedef struct __wait_queue_head wait_queue_head_t;
sleep_on( ) function void sleep_on(wait_queue_head_t *q) { unsigned long flags; wait_queue_t wait; wait.flags = 0; wait.task = current; current->state = TASK_UNINTERRUPTIBLE; add_wait_queue(q,&wait); schedule(); remove_wait_queue(q,&wait); }
More wait functions • wait_event_interruptible(wq, condition) • { • wait_queue_t __wait; • init_waitqueue_entry(&__wait, current); • add_wait_queue(&wq, &__wait); • for(;;) { • set_current_state(TASK_INTERRUPTIBLE); • if(condition) break; • schedule(); • } • current->state = TASK_RUNNING; • remove_wait_queue(&wq, &__wait); • }
Even more wait functions void wake_up(wait_queue_head_t *q) { struct list_head *tmp; wait_queue_t *curr; list_for_each(tmp, &q->task_list){ curr=list_entry(tmp, wait_queue_t, task_list); wake_up_process(curr->task); if(curr->flags) break; } }
Process Resource Limits • RLIMIT_AS • max address space • RLIMIT_CORE • max coredump size • RLIMIT_CPU • max cputime for the process in seconds • RLIMIT_DATA • max heap size • RLIMIT_FSIZE • max file size • RLIMIT_LOCKS • max # of locks
Process Resource Limits • RLIMIT_MEMLOCK • max non-swappable memory • RLIMIT_NOFILE • max open file descriptors • RLIMIT_NPROC • number of child processes • RLIMIT_RSS • max # page frames • RLIMIT_STACK • max stack size
Process Switch • Hardware context • value of all registers and flags for process • Task State Segment (TSS) • specific segment type to store hardware contexts • Needs TSS because • switches from user mode to kernel mode fetch the address of kernel mode stack from TSS • Access of IO port requires access of IO permission bitmap stored in TSS • Thread Field • on process switch the hardware context of the process being replaced is saved • Can’t be saved on the TSS cuz don’t know when process will wake up and which CPU will wake it. • each process descriptor has a thread field which stores the hardware context
Performing the context switch • Two main steps • Switching the page global directory to install a new address space • switching the kernel mode stack and hardware context • Two pointers in schedule() function • prev (process being saved) • next (process being activated)
switch_to macro • 1. save the values of prev and next in eax and edx registers • 2. save another copy of prev in ebx • 3. save the contents of esi, edi, and ebp registers in the prev kernel mode stack • 4. save content of esp in prev->thread.esp • 5. load next->thread.esp in esp • From now on we are use next’s kernel mode stack • 6. Save current address in prev->thread.eip • 7. push next->thread.eip to next kernel stack • 8. jumps to the switch_to( ) function • takes the value of prev and next from registers not from stack
The __switch_to function • save contents of FPU, MMX, and XMMregisters • loads next->esp0 to TSS • privilege level of next process • Stores fs and gs segmentation registers in prev • loads fs and gs segment registers from next • loads the six debug registers from next • Updates the IO bitmap in the TSS • Terminates • Issues a ret • value was previously pushed by the switch to macro
Creating Processes • Much time is wasted when a fork() call is issued to copy the parent context to the child process • This problem is solved by • copy on write allows both parent and child to read the same physical pages` • Lightweight process allow both the parent and child to share many process kernel data structures • vfork() command creates a process that shares the memory address space of its parent • parent’s execution is blocked until child exits
do_fork() • when either a clone(), fork() or vfork()is called the kernel invokes do_fork() • does several things including: • get space for process descriptor • get parent process’s info • check resources to see if process will fit • update descriptor fields in child that are different than parent’s • copy’s the parents file descriptors etc. • initialize the kernel thread of the child process • if process is lightweight inserts process into thread group • calls hash_pid to put process in hash table • put process in processes list • set status to task_running • suspends parent process if necessary • returns pid of child
Kernel Threads • Each kernel thread executes a single specific kernel C function • regular processes execute kernel functions only through system calls • Kernel threads run only in Kernel Mode • regular processes execute in user mode or in kernel mode • They use only linear addresses greater than PAGE_OFFSET • regular processes use the whole address space
Creating a Kernel thread int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags) { p = clone(0, flags | CLONE_VM); if ( p ) // parent return p; else { // child fn(arg); exit(); }
Some special processes • Process 0 • swapper process • started from scratch in start_kernel() • runs cpu_idle( ) when no other processes are running • Process 1 • init process • shares all per-process kernel data with process 0 • executes the init() function which continues initialization of the kernel
Destroying processes • handled by do_exit() function • performs the following • sets the PF_EXITING flag in process descriptor • removes the process from any wait queues • closes any open resources • files, signal handles etc. • sets the exit_code field in the process descriptor to process termination code • calls exit_notify() to notify parent of child’s demise • calls schedule( )
Removing a process • Parents often create children to perform certain tasks, and know the task is complete by the exit code given by the child • the ZOMBIE status is given to those process that are terminated but not yet removed to give time for the process to notify the parent. • If a process is terminated before it’s children, the children get process 1 as their parent.