-
clone()-fork()-exec()-exit() 子进程结束ZOMBIE 父进程wait4()
进程描述符 task_struct进程所有信息 由thread_info分配。为了提高current宏找进程描述符的速度,方便使用偏移量计算进程描述符 -
进程状态
TASK_RUNNING
TASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE
TASK_ZOMBIE
TASK_STOP -
用户空间——sys_Call /异常——(进程上下文)内核空间
-
copy-on-write 写时拷贝其他以只读方式共享 fork()分配子进程页表和task_struck. vfork()不拷贝页表,其余同fork,在无写时拷贝时代有利。如今相差无几
进程创建
Unix:fork() 和exec() 首先fork() 拷贝父进程,区别pid和资源和统计量。fork()实际开销是复制父进程的页表以及给子进程创建唯一的进程描述符。exec()读取可执行文件载入地址空间运行。
- clone() -> do_fork() -> copy_process():
-
- dup_task_struck() 与父进程完全相同的内核栈 thread_info task_struct
-
- 检查总进程数量小于max
-
- 区分父子进程:task_struct 成员清零或初始值
-
- 子进程 TASK_UNINTERRUPIBLE 不被执行
-
- 更新进程描述符的flag成员 copy_flags() PF_SUPERPRIV=0 设置PF_FORKNOEXEC(未执行标志)
-
- 分配alloc_pid()
-
- 从clone()的输入参数中确定新进程行为方式和父子进程之间共享的资源种类 eg: clone(CLONE_VM| CLONE_FS | CLONE_SIGHAND,0)
-
- 扫尾,返回do_fork()指向子进程的指针
-
copy_process-复制父进程的内核栈进程描述符等-检查进程数量-进程描述符设置初始值-设置UNINTERRUPTBLE-更新flags-分配pid-分配共享资源-返回指针给do_fork()
- 内核线程区别在于无独立地址空间 mm指针-> NULL 只在内核空间运行且由内核线程生成。 调用do_exit()退出,或内核其他部分调用kthread_stop()退出
进程终结
- do_exit():
-
- 在进程描述符task_struct 标志成员PF_EXITING
-
- del_time_sync() 删除内核定时器
-
- 若有BSD 进程记账功能 输出信息
-
- exit_mm() 释放占用的mm_struct 若无其他进程使用此地址空间则彻底释放
-
- sem_exit() 释放信号
-
- exit_files() exit_fs() 递减引用计数,若某引用计数为0释放资源
-
- exit_code() 设为exit()的退出代码。供父进程检索
-
- exit_notify()告诉父进程,给子进程找养父。进程状态设为EXIT_ZOMBIE
-
- do_exit()调用schedule()切换到其他进程。不返回
-
- 此时占有的内存为内核栈、task_struct、thread_info结构。此时进程存在的意义是向他的父进程提供信息。待父进程释放。
do_exit()-进程描述符设置退出标志-释放地址空间-释放信号队列-释放文件计数器-退出代码-告父,子进程找养父-EXIT_ZOMBIE
删除进程描述符
由父进程释放, wait() 族调用唯一的系统调用wait4()函数来实现。wait4挂起调用它的进程,直到其中一个子进程退出,返回该子进程的PID。 调用wait4函数的指针包含了子函数的退出代码。
释放进程描述符时,release_task()函数会被调用完成以下工作
-
- 调用 _exit_signal() -> _unhash_process() ->detach_pid()从pidhash中删除该进程,任务列表中也删除
-
- _exit_signal()释放目前僵死进程所有剩余资源。统计记录
-
- 调用put_task_struct()释放进程内核栈和thread_info所占的页,释放task_struct所占的slab高速缓存
孤儿进程
父进程在子进程之前退出,不找养父则子进程退出一直是僵死态,占用内存。
解决方法:当前线程组找新父亲,不行则用init进程。exit_notify()-> forget_original_parent()->find_new_reaper()
寻父
*find_new_reaper(*father){
*pid_ns=(father);
task_struct *thread =father;
while_each_thread(father,thread){
if(flags & PF_EXITING)
/*若标识符和被设为退出状态 则跳过*/
if(unlikely(pid_ns->child_reaper==father))
pid_ns->child_reaper=thread;
/*线程组有其他线程*/
return thread;
}
if(unlikely(pid_ns->child_reaper==father)){
获得tasklist_lock写锁
/*设置init进程为继父*/
if(unlikely(pid_ns==&init_pid_ns))
zap_pid_ns_processes(pid_ns);
tasklist_lock 上锁
pid_ns->child_reaper=init_pid_ns.child_reaper;
}
return pid_ns->child_reaper;
}
遍历所有子进程设置新的父进程
![](/qrcode.jpg)
reaper=find_new_reaper(father);
list_for_each_entry_safe(p,n,&father->children,sibling){
p->real_parent=reaper; //real_parent 是继父
if(p->parent==father){
BUG_ON(p->ptrace);
p->parent=p->real_parent;
}
reparent_thread(p.father);
}
然后调用ptrace_exit_finish()同样寻父,给ptraced的子进程寻父
void exit_ptrace(struct task_struct *tracer){
struct task_struct *p, *n;
LIST_HEAD(ptrace_dead);
// 获得tasklist_lock写锁
list_for_each_entry_safe(p,n,&tracer->ptraced ,ptrace_entry){//子进程列表
if(_ptrace_detach(tracer,p))
list_add(&p->ptrace_entry.&ptrace_dead);
// 释放tasklist_lock 锁
}
BUG_ON(! list_empty(&tracer->ptraced));
list_for_each_entry_safe(p,n,&ptrace_dead ,ptrace_entry){// ptrace子进程列表
list_del_init(&p->ptrace_entry);
release_task(p);
}
}
当一个进程被跟踪时,他的临时父亲设定为调试进程。此时他的父进程退出了,系统会为它和它的所有兄弟进程重新找一个父进程。之前的内核会遍历系统所有进程来找这些子进程。现在的解决方法是在一个单独被ptrace跟踪的子进程链表中搜索相关的兄弟进程。用两个相对较小的表减轻遍历的消耗。
init进程例行调用wait()检查进程。清除僵死进程。
小结
copy_process - 复制父进程的内核栈进程描述符等 - 检查进程数量 - 进程描述符设置初始值 - 设置UNINTERRUPTBLE - 更新flags - 分配pid - 分配共享资源 - 返回指针给do_fork()
do_exit() - 进程描述符设置退出标志 - 释放地址空间 - 释放信号队列 - 释放文件计数器 - 退出代码 - 告父,子进程找养父 - EXIT_ZOMBIE
linux
如何存放和表示进程(task_struct和 thread_info)
如何创建进程 (通过fork()实际上最终是clone())
如何把新的执行映像装入地址空间(通过exec()系统调用族)
进程如何消亡(强制或自愿的调用exit())