免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1125 | 回复: 0

Chapter 3. Process Management [复制链接]

论坛徽章:
0
发表于 2010-02-06 19:03 |显示全部楼层
Chapter 3. Process Management
第3章.进程管理
The process is one of the fundamental abstractions in Unix operating systems
[1]
. A process is a program (object code stored on some media) in execution. Processes are, however, more than just the executing program code (often called the text section in Unix). They also include a set of resources such as open files and pending signals, internal kernel data, processor state, an address space, one or more threads of execution, and a data section containing global variables. Processes, in effect, are the living result of running program code.
进程是Unix系统最基本的抽象之一。一个进程就是执行期的程序(目标代码存储在媒体上)。但是进程远远不止包括执行期的程序(在Unix中称为代码段)。它们也括一套资源,比如打开文件、挂起的信号、内部内核数据、进程状态、地址空间、一条或者多条线程和包括全局变量的数据段等。实际上,进程就是正在执行程序代码的活标本。
[1] The other fundamental abstraction is files.
[1]另一个基本抽象是文件。
Threads of execution, often shortened to threads, are the objects of activity within the process. Each thread includes a unique program counter, process stack, and set of processor registers. The kernel schedules individual threads, not processes. In traditional Unix systems, each process consists of one thread. In modern systems, however, multithreaded programsthose that consist of more than one threadare common. As you will see later, Linux has a unique implementation of threads: It does not differentiate between threads and processes. To Linux, a thread is just a special kind of process.
执行线程,简称线程,是进程中活动的对象。每条线程包括一个统一的程序计数器、进程栈和一组进程寄存器。内核调度的对象是线程,不是进程。在传统的Unix系统中,每个进程包括一个线程。然而,在现在的系统中,包含多条线程的多线程程序司空见惯。在后面的章节你可以看到,Linux有一套独特的线程执行机制:它不区分进程与线程。对Linux而言,一条线程就是一种特殊的进程。
On modern operating systems, processes provide two virtualizations: a virtualized processor and virtual memory. The virtual processor gives the process the illusion that it alone monopolizes the system, despite possibly sharing the processor among dozens of other processes.
Chapter 4
, "Process Scheduling," discusses this virtualization. Virtual memory lets the process allocate and manage memory as if it alone owned all the memory in the system. Virtual memory is covered in
Chapter 11
, "Memory Management." Interestingly, note that threads share the virtual memory abstraction while each receives its own virtualized processor.
在现代的系统中,进程提供两种虚拟机制:虚拟进程和虚拟内存。尽管与其它的进程共享了资源,虚拟进程还是给进程一种假象,让它独占系统。在第4章的进程调度中讨论这种虚拟化。虚拟内存为进程分派和管理内存,就像它在系统中获得了整个内存。虚拟内存将在第11章内存管理讨论。有趣的是,注意在线程之间可以共享虚拟内存,但是各自拥用自己的虚拟处理器。
A program itself is not a process; a process is an active program and related resources. Indeed, two or more processes can exist that are executing the same program. In fact, two or more processes can exist that share various resources, such as open files or an address space.
程序本身不是进程;进程是处理执行期的程序和所包含的资源。事实上,两个或多个进程可以同进存在,且正在执行同一个程序。两个或者多个并存的进程能够存在,且分享不同的资源,比如打开的文件和地址空间等。
A process begins its life when, not surprisingly, it is created. In Linux, this occurs by means of the fork() system call, which creates a new process by duplicating an existing one. The process that calls fork() is the parent, whereas the new process is the child. The parent resumes execution and the child starts execution at the same place, where the call returns. The fork() system call returns from the kernel twice: once in the parent process and again in the newborn child.
无疑,进程在它被创建的时候开始存活。在Linux系统中,进程的创建是通过fork()系统调用,它复制一个存在的进程来创建一个新的进程。被fork()调用的进程是父进程,新的进程是子进程。在调用返回的地方,父进程继续执行,子进程开始执行。fork()调用从内核返回两次, 一次返回在父进程,另一次返回在新诞生的子进程中。
Often, immediately after a fork it is desirable to execute a new, different, program. The exec*() family of function calls is used to create a new address space and load a new program into it. In modern Linux kernels, fork() is actually implemented via the clone() system call, which is discussed in a following section.
一般,创建进程是为了立即执行新的、不同的程序。exec*()函数族被调用,用来创建新的地址空间,并加载一个新的程序到里面去。在现在的Linux内核中,实际上fork()函数是通过clone()系统调用来实现,接下来的章节将要讨论它。
Finally, a program exits via the exit() system call. This function terminates the process and frees all its resources. A parent process can inquire about the status of a terminated child via the wait4()
[2]
system call, which enables a process to wait for the termination of a specific process. When a process exits, it is placed into a special zombie state that is used to represent terminated processes until the parent calls wait() or waitpid().
最后,程序退出是通过exit()系统调用。函数终结进程并且释放它所有的资源。父进程通过wait4()系统调用,来查询子进程是否终结,这其实使得一个进程等待特定进程执行终结的能力。当一个进程退出,它将被设置在一个特定的僵死状态,这样可以用来描述终结的进程,直到它的父进程调用wait()或waitpid()为止。
[2] The kernel implements the wait4() system call. Linux systems, via the C library, typically provide the wait(),waitpid(),wait3() , and wait4() functions. All these functions return status about a terminated process, albeit with slightly different semantics.
[2] 内核实现wait4()系统调用。Linux系统通过C函数库通常要提供函数wait(),waitpid(),wait3(),和wait4()。所有这些函数返回终结进程的状态,即使有些细小的语义差别。
Another name for a process is a task. The Linux kernel internally refers to processes as tasks. In this book, I will use the terms interchangeably, although when I say task I am generally referring to a process from the kernel's point of view.
进程的另一个名字是任务。在Linux 内核中,进程可以认为是任务。在本书中,我将交替的使用这两种术语。不过我所说到的任务,通常是从内核观点所看到的进程。


Process Descriptor and the Task Structure
进程描述符及任务结构体
The kernel stores the list of processes in a circular doubly linked list called the task list
[3]
. Each element in the task list is a process descriptor of the type struct task_struct, which is defined in . The process descriptor contains all the information about a specific process.
内核将进程队列存储在一个叫task list的双向链表中。task list中的每一个元素是struct task_struct类型的进程描述符,该结构被定义在。进程描述符包含一个具体进程的所有信息。
[3] Some texts on operating system design call this list the task array. Because the Linux implementation is a linked list and not a static array, it is called the task list.
[3] 有些关于操作系统的教材称这个队列为任务数组。因为Linux的实现是链表而不是静态数组,所以被称之为tast list。
The task_struct is a relatively large data structure, at around 1.7 kilobytes on a 32-bit machine. This size, however, is quite small considering that the structure contains all the information that the kernel has and needs about a process. The process descriptor contains the data that describes the executing program open files, the process's address space, pending signals, the process's state, and much more (see
Figure 3.1
).
task_struct是一个相对较大的数据结构,在32位机器上约1.7Kbytes。但是考滤到这个结构包含着内核有的和所需要的所有信息,这个大小也算得上非常不的了。这个进程描述符包含数据,如打开的文件、进程地址空间、挂起的信号、进程的状态等等(见3.1节),来描述正在执行的程序。
Figure 3.1. The process descriptor and task list.

Allocating the Process Descriptor
分配进程描述符
The task_struct structure is allocated via the slab allocator to provide object reuse and cache coloring (see
Chapter 11
, "Memory Management"). Prior to the 2.6 kernel series, struct task_struct was stored at the end of the kernel stack of each process. This allowed architectures with few registers, such as x86, to calculate the location of the process descriptor via the stack pointer without using an extra register to store the location. With the process descriptor now dynamically created via the slab allocator, a new structure, struct thread_info, was created that again lives at the bottom of the stack (for stacks that grow down) and at the top of the stack (for stacks that grow up)
[4]
. See
Figure 3.2
. The new structure also makes it rather easy to calculate offsets of its values for use in assembly code.
Linux通过slab分配器分配结构体task_struct来提供对象利用各角色缓存(见第11章内存管理)。
[4] Register-impaired architectures were not the only reason for creating struct thread_info.
Figure 3.2. The process descriptor and kernel stack.

The thread_info structure is defined on x86 in  as
struct thread_info {        struct task_struct    *task;        struct exec_domain    *exec_domain;        unsigned long         flags;        unsigned long         status;        __u32                 cpu;        __s32                 preempt_count;        mm_segment_t          addr_limit;        struct restart_block  restart_block;        unsigned long         previous_esp;        __u8                  supervisor_stack[0];};

Each task's tHRead_info structure is allocated at the end of its stack. The task element of the structure is a pointer to the task's actual task_struct.
Storing the Process Descriptor
The system identifies processes by a unique process identification value or PID. The PID is a numerical value that is represented by the opaque type
[5]
pid_t, which is typically an int. Because of backward compatibility with earlier Unix and Linux versions, however, the default maximum value is only 32,768 (that of a short int), although the value can optionally be increased to the full range afforded the type. The kernel stores this value as pid inside each process descriptor.
[5] An opaque type is a data type whose physical representation is unknown or irrelevant.
This maximum value is important because it is essentially the maximum number of processes that may exist concurrently on the system. Although 32,768 might be sufficient for a desktop system, large servers may require many more processes. The lower the value, the sooner the values will wrap around, destroying the useful notion that higher values indicate later run processes than lower values. If the system is willing to break compatibility with old applications, the administrator may increase the maximum value via /proc/sys/kernel/pid_max.
Inside the kernel, tasks are typically referenced directly by a pointer to their task_struct structure. In fact, most kernel code that deals with processes works directly with struct task_struct. Consequently, it is very useful to be able to quickly look up the process descriptor of the currently executing task, which is done via the current macro. This macro must be separately implemented by each architecture. Some architectures save a pointer to the task_struct structure of the currently running process in a register, allowing for efficient access. Other architectures, such as x86 (which has few registers to waste), make use of the fact that struct thread_info is stored on the kernel stack to calculate the location of thread_info and subsequently the task_struct.
On x86, current is calculated by masking out the 13 least significant bits of the stack pointer to obtain the thread_info structure. This is done by the current_thread_info() function. The assembly is shown here:
movl $-8192, %eaxandl %esp, %eax

This assumes that the stack size is 8KB. When 4KB stacks are enabled, 4096 is used in lieu of 8192.
Finally, current dereferences the task member of thread_info to return the task_struct:
current_thread_info()->task;

Contrast this approach with that taken by PowerPC (IBM's modern RISC-based microprocessor), which stores the current task_struct in a register. Thus, current on PPC merely returns the value stored in the register r2. PPC can take this approach because, unlike x86, it has plenty of registers. Because accessing the process descriptor is a common and important job, the PPC kernel developers deem using a register worthy for the task.
Process State
The state field of the process descriptor describes the current condition of the process (see
Figure 3.3
). Each process on the system is in exactly one of five different states. This value is represented by one of five flags:
·         TASK_RUNNING The process is runnable; it is either currently running or on a runqueue waiting to run (runqueues are discussed in
Chapter 4
, "
Scheduling
"). This is the only possible state for a process executing in user-space; it can also apply to a process in kernel-space that is actively running.
·         TASK_INTERRUPTIBLE The process is sleeping (that is, it is blocked), waiting for some condition to exist. When this condition exists, the kernel sets the process's state to TASK_RUNNING. The process also awakes prematurely and becomes runnable if it receives a signal.
·         TASK_UNINTERRUPTIBLE This state is identical to TASK_INTERRUPTIBLE except that it does not wake up and become runnable if it receives a signal. This is used in situations where the process must wait without interruption or when the event is expected to occur quite quickly. Because the task does not respond to signals in this state, TASK_UNINTERRUPTIBLE is less often used than TASK_INTERRUPTIBLE
[6]
.
[6] This is why you have those dreaded unkillable processes with state D in ps(1). Because the task will not respond to signals, you cannot send it a SIGKILL signal. Further, even if you could terminate the task, it would not be wise as the task is supposedly in the middle of an important operation and may hold a semaphore.
·         TASK_ZOMBIE The task has terminated, but its parent has not yet issued a wait4() system call. The task's process descriptor must remain in case the parent wants to access it. If the parent calls wait4(), the process descriptor is deallocated.
·         TASK_STOPPED Process execution has stopped; the task is not running nor is it eligible to run. This occurs if the task receives the SIGSTOP, SIGTSTP, SIGTTIN, or SIGTTOU signal or if it receives any signal while it is being debugged.
Figure 3.3. Flow chart of process states.

Manipulating the Current Process State
Kernel code often needs to change a process's state. The preferred mechanism is using
set_task_state(task, state);        /* set task 'task' to state 'state' */

This function sets the given task to the given state. If applicable, it also provides a memory barrier to force ordering on other processors (this is only needed on SMP systems). Otherwise, it is equivalent to
task->state = state;

The method set_current_state(state) is synonymous to set_task_state(current, state).
Process Context
One of the most important parts of a process is the executing program code. This code is read in from an executable file and executed within the program's address space. Normal program execution occurs in user-space. When a program executes a system call (see
Chapter 5
, "System Calls") or triggers an exception, it enters kernel-space. At this point, the kernel is said to be "executing on behalf of the process" and is in process context. When in process context, the current macro is valid
[7]
. Upon exiting the kernel, the process resumes execution in user-space, unless a higher-priority process has become runnable in the interim, in which case the scheduler is invoked to select the higher priority process.
[7] Other than process context there is interrupt context, which we discuss in
Chapter 6
, "Interrupts and Interrupt Handlers." In interrupt context, the system is not running on behalf of a process, but is executing an interrupt handler. There is no process tied to interrupt handlers and consequently no process context.
System calls and exception handlers are well-defined interfaces into the kernel. A process can begin executing in kernel-space only through one of these interfacesall access to the kernel is through these interfaces.
The Process Family Tree
A distinct hierarchy exists between processes in Unix systems, and Linux is no exception. All processes are descendents of the init process, whose PID is one. The kernel starts init in the last step of the boot process. The init process, in turn, reads the system initscripts and executes more programs, eventually completing the boot process.
Every process on the system has exactly one parent. Likewise, every process has zero or more children. Processes that are all direct children of the same parent are called siblings. The relationship between processes is stored in the process descriptor. Each task_struct has a pointer to the parent's task_struct, named parent, and a list of children, named children. Consequently, given the current process, it is possible to obtain the process descriptor of its parent with the following code:
struct task_struct *my_parent = current->parent;

Similarly, it is possible to iterate over a process's children with
struct task_struct *task;struct list_head *list; list_for_each(list, &current->children) {        task = list_entry(list, struct task_struct, sibling);        /* task now points to one of current's children */}

The init task's process descriptor is statically allocated as init_task. A good example of the relationship between all processes is the fact that this code will always succeed:
struct task_struct *task; for (task = current; task != &init_task; task = task->parent)        ;/* task now points to init */

In fact, you can follow the process hierarchy from any one process in the system to any other. Oftentimes, however, it is desirable simply to iterate over all processes in the system. This is easy because the task list is a circular doubly linked list. To obtain the next task in the list, given any valid task, use:
list_entry(task->tasks.next, struct task_struct, tasks)

Obtaining the previous works the same way:
list_entry(task->tasks.prev, struct task_struct, tasks)

These two routines are provided by the macros next_task(task) and prev_task(task), respectively. Finally, the macro for_each_process(task) is provided, which iterates over the entire task list. On each iteration, task points to the next task in the list:
struct task_struct *task; for_each_process(task) {        /* this pointlessly prints the name and PID of each task */        printk("%s[%d]\n", task->comm, task->pid);}

Note: It can be expensive to iterate over every task in a system with many processes; code should have good reason (and no alternative) before doing so.
[/url]


本文来自ChinaUnix博客,如果查看原文请点:[url]http://blog.chinaunix.net/u3/105531/showart_2179157.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

SACC2019中国系统架构师大会

【数字转型 架构演进】SACC2019中国系统架构师大会
2019年10月31日~11月2日第11届中国系统架构师大会(SACC2019)将在北京隆重召开。四大主线并行的演讲模式,1个主会场、20个技术专场、超千人参与的会议规模,100+来自互联网、金融、制造业、电商等领域的嘉宾阵容,将为广大参会者提供一场最具价值的技术交流盛会。




----------------------------------------

大会官网>>
  

北京盛拓优讯信息技术有限公司. 版权所有 16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122
中国互联网协会会员  联系我们:huangweiwei@it168.com
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP