Blog
Blog copied to clipboard
Chapter 3 Process Management
进程
进程是程序的运行态;包含各种资源,如文件,信号,内存等。线程(Threads)是内核的调度单位。
进程创建
进程通过fork
系统调用复制一份(除了pending signals
)现有进程创建出来。fork
会返回两次,一次在父进程中,一次在新生进程中。
在fork
后,通常需要运行新的程序,这通过exec
族函数来实现;exec
会创建新的address space
来加载新程序。
Copy-On-Write
传统行为上,一旦fork,父进程的资源都会拷贝一份给子进程,这太低效了。在Linux中,fork
使得父子进程可以以只读方式共享资源,当写的时候会触发异常然后将对应数据拷贝一份。fork
的唯一开销是父进程的页表也会复制一份。
Fork流程
fork
实际上是调用clone
系统调用传入不同的Flag,最终调用到do_fork
,再调用copy_process
:
- 调用
dup_task_struct()
,给新进程创建新的内核栈 - 然后检查新进程是否超过当前用户的进程数资源限制
-
task_struct
中部分资源清理或设置为初始值,以此和父进程区分开 - 子进程状态被设置为
TASK_UNINTERRUPTIBLE
-
copy_process()
调用copy_flags()
更新task_struct
的flags
,代表超级权限的PF_SUPERPRIV
被清理,表示没有调用exec的PF_FORKNOEXEC
被设置 - 调用
alloc_pid()
分配新的PID - 根据
clone()
传入的参数不同,来拷贝不同的资源 - 最后,
copy_process()
返回新的进程的指针
如果copy_process
成功返回,新进程会立即唤醒并执行。内核故意让子进程优先运行。这是考虑到一个简单的场景。在子进程简单地立即调用 exec() 的常见情况下,这消除了如果父进程首先运行并开始写入地址空间时会发生的任何写时复制开销”
进程退出
进程退出时会调用exit
系统调用,这个函数会终止进程并进行资源回收,父进程可以用wait
来获取子进程的终止状态。
进程描述符
内核将进程存储在称为task list
的双向循环链表中,struct task_struct
描述了进程包含的所有信息,task_struct
通过slab allocator
分配。
系统通过
PID
来标识每一个进程,系统支持的最大进程数可通过/proc/sys/kernel/pid_max
调整。系统通过current
来获取当前进程。
进程状态
task_struct
的state
域描述了当前进程的状态,由下面五个Flag标识。
- TASK_RUNNING 进程处于running态,或者在run-queue上waiting to run
- TASK_INTERRUPTIBLE 进程处于睡眠态,可以接收信号
- TASK_UNINTERRUPTIBLE 进程也是睡眠态,但不能接收信号
- __TASK_TRACED 进程正在被调试追踪,比如
ptrace
- __TASK_STOPPED 进程停止执行,也不能继续执行,当进程收到如
SIGSTOP
,SIGTSTP
,SIGTTIN
,SIGTTOU
,或者在调试模式下收到任何信号。
进程状态转换关系如下
可以使用如下接口函数设置进程状态:
set_task_state(task, state) /* set task ‘task’ to state ‘state’ */
set_current_state(state) /* set current task to state ‘state’ */
相比于直接task->state = **state;
,set_current_state
提供了内存屏障机制来保证在SMP情况下同步
进程上下文
什么是进程上下文?
When a program executes a system call 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
系统调用和异常处理是用户态程序进入内核态的唯一接口。
进程树
所有进程都是init
进程的后代,其PID为1。task_struct
的parent
域指向父进程,children
域是指向子进程的链表,如下代码片遍历进程所有的子进程。
struct task_struct *task;
struct list_head *list;
list_for_each(list, ¤t->children) {
task = list_entry(list, struct task_struct, sibling);
/* task now points to one of current’s children */
}
遍历到init
进程
struct task_struct *task;
for (task = current; task != &init_task; task = task->parent)
;
/* task now points to init */
遍历所有进程
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);
}
线程
创建线程
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)
Clone标志位
内核线程
内核线程没有address space
,也就是mm= NULL
,内核线程的创建都是通过fork kthreadd
来实现的。
// <linux/kthread.h>
struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[],...)
通过这个接口创建的内核线程是unrunnable state,除非调用了wake_up_process
。内核线程可以如下接口
struct task_struct *kthread_run(int (*threadfn)(void *data),
void *data,
const char namefmt[],
...)
来实现创建后runnable。其实现如下
#define kthread_run(threadfn, data, namefmt, ...) \
({ \
struct task_struct *k; \
\
k = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(k)) \
wake_up_process(k); \
k; \
})
kernel线程会一直运行直到调用了do_exit()
或者kthread_stop()
进程终止
进程终止分为自愿(如调用exit
)和非自愿(如发生异常,或收到信号),但最终都会调用到do_exit
- 设置
task_struct
的PF_EXITING
flag - 调用
del_timer_sync()
删除定时器,确保没有定时器处理函数在运行 - 如果BSD记账被打开,调用
acct_update_integrals()
更新记账信息 - 调用
exit_mm
释放mm_struct
,如果没有其他进程(即不是share)在用、它,内核销毁它 - 调用
exit_sem
,删除相关IPC 信号量 - It then calls exit_files() and exit_fs() to decrement the usage count of objects related to file descriptors and filesystem data, respectively.
- 设置
exit_code
- 调用
exit_notify()
给父进程发生信号,reparents当前进程的其他子进程,设置exit_state
为EXIT_ZOMBIE
- 调用
schedule()
切换进程,do_exit
不会返回
当父进程手动子进程退出信息后,子进程的内核栈,task_struct所占的内存才会被释放,此时release_task()
被调用
- 调用
__exit_signal()
,然后调用__unhash_process()
,再调用detach_pid()
将进程从pidhash和task list中移除 -
__exit_signal()
释放剩余资源 - If the task was the last member of a thread group, and the leader is a zombie, then release_task() notifies the zombie leader’s parent
- release_task() calls put_task_struct() to free the pages containing the process’s kernel stack and thread_info structure and deallocate the slab cache containing the task_struct.