Documente Academic
Documente Profesional
Documente Cultură
CNS 4510
Processes
Threads
Modern Unix systems support multithreaded 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
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 IBMs Next Generation Posix Threading Package (NGPT)
Process Descriptor
Stores needed information for each process Implemented through the task_struct structure
Process State
Task_uninterruptible
Sleeping until some condition is true device driver probing for a certain hardware state Signals to this process leaves it unchanged
Task_Stopped Task_Zombie
Process Identification
Each process has a unique pid However, most of the time a process is referred to by its PDP
(Process Descriptor pointer)
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
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];
};
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 Dont need a separate current process pointer
#define for_each_task(p)
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()
Parenthood Relationships
p_pptr (parent)
points to 1 if parent no longer exists points to the current parent points to youngest child
p_cptr(child)
p_ysptr(younger 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
Useful functions
add_wait_queue( ) add_wait_queue_exclusive( ) remove_wait_queue() wait_queue_active()
DELCLARE_WAIT_QUEUE_HEAD(name)
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);
RLIMIT_AS
RLIMIT_CORE RLIMIT_CPU
max address space max coredump size max cputime for the process in seconds max heap size
RLIMIT_DATA
max file size
RLIMIT_FSIZE
RLIMIT_LOCKS
max # of locks
RLIMIT_MEMLOCK
RLIMIT_NOFILE
max non-swappable memory max open file descriptors number of child processes max # page frames max stack size
RLIMIT_NPROC
RLIMIT_RSS
RLIMIT_STACK
Process Switch
Hardware context
Task State Segment (TSS) Needs TSS because
value of all registers and flags for process specific segment type to store hardware contexts 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 on process switch the hardware context of the process being replaced is saved Cant be saved on the TSS cuz dont know when process will wake up and which CPU will wake it. each process descriptor has a thread field which stores the hardware context
Thread Field
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
6. Save current address in prev->thread.eip 7. push next->thread.eip to next kernel stack 8. jumps to the switch_to( ) function
From now on we are use nexts kernel mode stack
takes the value of prev and next from registers not from stack
save contents of FPU, MMX, and XMM registers loads next->esp0 to TSS
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 privilege level of next process
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
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 processs info check resources to see if process will fit update descriptor fields in child that are different than parents copys 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
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
sets the PF_EXITING flag in process descriptor removes the process from any wait queues closes any open resources
sets the exit_code field in the process descriptor to process termination code calls exit_notify() to notify parent of childs 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 its children, the children get process 1 as their parent.