点击(此处)折叠或打开
- #include <stdio.h>
- int main (int argc, char *argv[])
- {
- printf ("Hello World\n");
- return 0;
- }
保存为hello.c,我们可以通过gcc hello.c -o hello得到可执行程序hello.
由于我们认为main函数返回后,代表程序结束,实际上,当main函数返回的时候,后续还调用了一个exit函数(C库中实现)。
点击(此处)折叠或打开
- xxxxx
- {
- .......
- result = main(argc, argv);
- exit(result);
- }
点击(此处)折叠或打开
- SYSCALL_DEFINE1(exit, int, error_code)
- {
- do_exit((error_code&0xff)<<8);
- }
- // 源码在kernel/exit.c文件当中
点击(此处)折叠或打开
- exit (user space)
- ---|----------------------------------------------------------------------------
- v (kernel space)
- el0_sync (arm64同步异常中断处理函数)
- el0_svc (查找sys_call_table,获取到sys_execve函数地址,并运行)
- sys_exit
- do_exit
点击(此处)折叠或打开
- #define __noreturn __attribute__((noreturn))
- void __noreturn do_exit(long code)
- {
- ......
- exit_signals(tsk); // 设置PF_EXITING标识,告诉其他访问task_struct,当前task已经死亡
- // 现在流行薄葬啊,什么资源都要搜刮干净,赤裸而来赤裸而去
- tsk->exit_code = code; // 设置退出状态码,来自于exit()
- // 获取当前进程所在的线程组里是否还有成员活着。其中signal是signal_struct结构,Linux中,同一线程组里的所有线程共享信号资源。
- group_dead = atomic_dec_and_test(&tsk->signal->live);
- ..........
- exit_mm(tsk); // 会尝试去释放mm_struct结构
- exit_sem(tsk); // 释放信号量资源
- exit_shm(tsk); //释放共享内存资源
- exit_files(tsk); // 释放当前进程打开的文件资源put_files_struct(tsk->files)
- // 如果fs->users计数器为0,释放工作目录(root/pwd)对应资源free_fs_struct(tsk->fs)
- exit_fs(tsk);
- if (group_dead)
- disassociate_ctty(1);
- exit_task_namespaces(tsk); // 释放进程命名空间资源
- exit_task_work(tsk); // 依次执行由task_work_add增加到task->task_works的函数回调
- exit_thread(tsk); // CONFIG_HAVE_EXIT_THREAD配置是否需要执行
- sched_autogroup_exit_task(tsk);
- exit_notify(tsk, group_dead); // 收完死者值钱的东西后,通知亲属,处理后事
- do_task_dead(); // 设置当前状态为D状态, 切换进程,一去不复返
- // 这里永远不会允许
- }
- void __noreturn do_task_dead(void)
- {
- __set_current_state(TASK_DEAD);
- __schedule(false); //主动让出CPU,发生系统调度,等待完成切换后, 释放task_struct结构。
- for (;;)
- cpu_relax();
- }
下面详细描述一下关键函数exit_notify怎么处理后事的,在描述之前,先普及3个概念:
- 孤儿进程组:该组中每个成员的父进程要么是该组的一个成员,要么不是该组所属会话的成员。或者用另外一种表示一个进程组不是孤儿进程组的条件是:该组中有一个进程,其父进程在属于同一个会话的另一个组中
- 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程;
- 进程组:进程组,每个进程组有一个领头进程。进程组是一个或多个进程的集合,通常它们与一组作业相关联,可以接受来自同一终端的各种信号。
点击(此处)折叠或打开
- static void exit_notify(struct task_struct *tsk, int group_dead)
- {
- ......
- //因该tsk死亡了,这里需要为tsk的子进程选取新的父进程:学名叫刘备托孤,当然没有儿子
- //也就不用托孤了,这函数也会直接返回
- forget_original_parent(tsk, &dead);
- // 世事无常,这一脉的线程组里已经空了,那么如果这个死者所在的进程组是孤儿进程组,则向这个孤儿组内还有stop job的线程发送SIGHUP和SIGCONT信号吧
- if (group_dead)
- kill_orphaned_pgrp(tsk->group_leader, NULL);
- if (unlikely(tsk->ptrace)) { //当前进程被trace
- int sig = thread_group_leader(tsk) && thread_group_empty(tsk) &&
- !ptrace_reparented(tsk) ?
- tsk->exit_signal : SIGCHLD;
- autoreap = do_notify_parent(tsk, sig);
- } else if (thread_group_leader(tsk)) {
- //当前线程为线程组组长,如果线程组不为空,就不要通知组长的父进程它已经死了的消息,即autoreap=false
- // 即这个线程组即使为僵尸,也不回收它的资源
- autoreap = thread_group_empty(tsk) &&
- // 如果线程组为空,就通知它的父进程,死亡原因(主要是回收资源相关)
- do_notify_parent(tsk, tsk->exit_signal);
- } else {
- // 当前线程不是线程组组长,直接设置autoreap为true,宣布dead,回收资源
- autoreap = true;
- }
- // 这里如果do_notify_parent返回是autoreap =true即EXIT_DEAD,说明父进程不需要子进程的退出码,即父进程不需要调用wait/waitpid等函数;否则就设置成EXIT_ZOMBIE,等父进程调用了wait函数后,再释放这个进程的task_struct
- tsk->exit_state = autoreap ? EXIT_DEAD : EXIT_ZOMBIE;
- // 如果进程已死,父进程不需要waitpid, 就放到资源释放链表dead里面去,等待释放
- if (tsk->exit_state == EXIT_DEAD)
- list_add(&tsk->ptrace_entry, &dead);
- // 引用计数小于0,唤醒线程组退出task去执行
- if (unlikely(tsk->signal->notify_count < 0))
- wake_up_process(tsk->signal->group_exit_task);
- write_unlock_irq(&tasklist_lock);
- // 遍历资源释放链表dead,为每一个在表里的释放资源,如果释放到当前线程,并且线程已空,而
- // 且线程组组长也是僵尸,那么久将线程组组长一起释放了
- list_for_each_entry_safe(p, n, &dead, ptrace_entry) {
- list_del_init(&p->ptrace_entry);
- // 尝试释放资源
- release_task(p);
- }
- }
点击(此处)折叠或打开
- static void forget_original_parent(struct task_struct *father,
- struct list_head *dead)
- {
- struct task_struct *p, *t, *reaper;
- //当前进程如果为ptrace,则退出所有ptrace下面的进程
- if (unlikely(!list_empty(&father->ptraced))) {
- exit_ptrace(father, dead);//退出所有被trace的task,并将已死的进程加入到dead链表
- }
- /* 选择合适的默认收养者,继父,通常为当前pid空间的1号进程,我们叫它村长 */
- reaper = find_child_reaper(father);
- if (list_empty(&father->children)) // 当前进程没有子进程,就直接返回了,不需要托孤了
- return;
- // 作为该pid空间的默认收养者,虽然有承担收养遗孤的责任,但是也不能什么孤儿都往这里放啊,还是要先看看孤儿的亲戚有没有愿意收养的吧
- // 尝试从亲戚中挑选一个收养者,pid命名空间的1号进程(村长)作为缺省的收养者,
- reaper = find_new_reaper(father, reaper);
- // 将当前进程下的所有娃儿的父亲都改成reaper(这个reaper可能是村长,也可能是他的祖先)。
- list_for_each_entry(p, &father->children, sibling) {
- for_each_thread(p, t) {
- t->real_parent = reaper; // 设置新的父进程real_parent ,在有调试的时候,parent 指向ptrace进程
- BUG_ON((!t->ptrace) != (t->parent == father));
- if (likely(!t->ptrace))
- t->parent = t->real_parent;
- if (t->pdeath_signal)
- group_send_sig_info(t->pdeath_signal,
- SEND_SIG_NOINFO, t);
- }
- // 如果死者和继父不是同一个线程组(不是一家)
- // (继父这里很可能就是init进程或者Pid空间的1号进程),那没办法,把死者的所有儿子状态为
- // EXIT_ZOMBIE的告诉它的继父;同时如果变成了孤儿组,则向孤儿组内所有还有stop jobs的进程
- // 发送SIGHUP和SIGCONT信号。
- if (!same_thread_group(reaper, father))
- reparent_leader(father, p, dead);
- }
- // 将当前进程的子进程 全部加到继父进程的链表上,从此这个继父就是这些子进程的父亲
- list_splice_tail_init(&father->children, &reaper->children);
- }
点击(此处)折叠或打开
- static struct task_struct *find_child_reaper(struct task_struct *father)
- __releases(&tasklist_lock)
- __acquires(&tasklist_lock)
- {
- struct pid_namespace *pid_ns = task_active_pid_ns(father);
- struct task_struct *reaper = pid_ns->child_reaper;
- // 查看死者是否为所在的pid空间的1号进程(村长) 如果不是,就返回pid空间的1号进程,作为缺省收养者
- if (likely(reaper != father))
- return reaper;
- // pid空间的1号进程就是死者,则需要从死者所在的线程组 选择一个亲戚作为备用收养者(下一个活动线程)
- // 哎,程序也讲究 世袭制,村长从亲戚中选
- reaper = find_alive_thread(father);
- if (reaper) {
- // 设置这个线程为当前pid空间的1号进程(村长)
- pid_ns->child_reaper = reaper;
- return reaper;
- }
- write_unlock_irq(&tasklist_lock);
- // 如果当前进程没有线程,并且自己又是1号进程,而且还是init_pid_ns,说明退出的是init进程,抛出异常
- // 创世神已死,留着世界干啥,panic掉
- if (unlikely(pid_ns == &init_pid_ns)) {
- panic("Attempted to kill init! exitcode=0x%08x\n",
- father->signal->group_exit_code ?: father->exit_code);
- }
- zap_pid_ns_processes(pid_ns);
- write_lock_irq(&tasklist_lock);
- // 死者继续上,没办法,不安宁
- return father;
- }
点击(此处)折叠或打开
- static struct task_struct *find_new_reaper(struct task_struct *father,
- struct task_struct *child_reaper)
- {
- struct task_struct *thread, *reaper;
- // child_reaper为find_child_reaper获取到的推荐收养者,但是收养者不同意,要求先从它的兄弟姐妹选择
- // 从当前进程的线程组(兄弟姐妹)选择一个alive的线程,如果线程存在,就用这个线程作为收养者
- thread = find_alive_thread(father);
- if (thread)
- return thread;
- // 这货没有兄弟姐妹,就只有找找他是否有遗属,说明他祖宗愿不愿意作为收养者,如果有,就往上去找他的祖宗来收养, has_child_subreaper为真,表示死者有交代他们家有祖先愿意做收养者
- if (father->signal->has_child_subreaper) {
- //从死者往上找,直到child_reaper为止,如果找到备胎愿意收养,就用这个祖宗线程组下
- //的线程作为收养者。如果找到init了,也没找到,那就只要让pid空间的1号来收养了,能者多劳嘛。
- for (reaper = father;
- !same_thread_group(reaper, child_reaper);
- reaper = reaper->real_parent) {
- /* 都找到创世神这里了,都没人愿意收养,只有跳出去找对应pid空间的1号进程了*/
- if (reaper == &init_task)
- break;
- // 这个祖先表明,他就是那个收养者,如果不是就continue
- if (!reaper->signal->is_child_subreaper)
- continue;
- // 找到一个合适的祖先,从这个辈分的祖先里面选一个或者的老者来收养
- thread = find_alive_thread(reaper);
- if (thread)
- return thread;
- }
- }
- // 村长:我曹,果然还是我,不让人省心啊
- return child_reaper;
- }
点击(此处)折叠或打开
- static void reparent_leader(struct task_struct *father, struct task_struct *p,
- struct list_head *dead)
- {
- // 进程已经死了,就直接返回了
- if (unlikely(p->exit_state == EXIT_DEAD))
- return;
- /* We don't want people slaying init. */
- p->exit_signal = SIGCHLD;
- // 查看进程是否为僵尸,并且所在线程组为空,就向它的新父亲,发送SIGCHLD信号
- if (!p->ptrace &&
- p->exit_state == EXIT_ZOMBIE && thread_group_empty(p)) {
- if (do_notify_parent(p, p->exit_signal)) {
- p->exit_state = EXIT_DEAD;
- list_add(&p->ptrace_entry, dead);
- }
- }
- // 如果是孤儿进组,则向进程组内所有还有stop jobs的进程发送SIGHUP和SIGCONT信号。
- kill_orphaned_pgrp(p, father);
- }
kill_orphaned_pgrp函数实现如下:
点击(此处)折叠或打开
- static void
- kill_orphaned_pgrp(struct task_struct *tsk, struct task_struct *parent)
- {
- struct pid *pgrp = task_pgrp(tsk);
- struct task_struct *ignored_task = tsk;
- if (!parent)
- /* exit: our father is in a different pgrp than
- * we are and we were the only connection outside.
- */
- parent = tsk->real_parent;
- else
- /* reparent: our child is in a different pgrp than
- * we are, and it was the only connection outside.
- */
- ignored_task = NULL;
- // UNIX中 在父进程终止后,进程组成为孤儿进程组,POSIX要求向新的孤儿进程组中处于停止状态的
- // 每一个进程发送挂断信号(SIGHUP),接着又向其发送继续信号(SIGCONT)。
- if (task_pgrp(parent) != pgrp &&
- task_session(parent) == task_session(tsk) &&
- will_become_orphaned_pgrp(pgrp, ignored_task) &&
- has_stopped_jobs(pgrp)) {
- __kill_pgrp_info(SIGHUP, SEND_SIG_PRIV, pgrp);
- __kill_pgrp_info(SIGCONT, SEND_SIG_PRIV, pgrp);
- }
- }
点击(此处)折叠或打开
- void release_task(struct task_struct *p)
- {
- struct task_struct *leader;
- int zap_leader;
- repeat:
- rcu_read_lock();
- atomic_dec(&__task_cred(p)->user->processes);
- rcu_read_unlock();
- proc_flush_task(p);
- write_lock_irq(&tasklist_lock);
- ptrace_release_task(p);
- __exit_signal(p);
- // 查看线程组是否已经没有线程存活了,如果已经空了,就通知线程组组长的父进程对组长进行收尸
- zap_leader = 0;
- leader = p->group_leader;
- if (leader != p && thread_group_empty(leader)
- && leader->exit_state == EXIT_ZOMBIE) {
- zap_leader = do_notify_parent(leader, leader->exit_signal);
- if (zap_leader)
- leader->exit_state = EXIT_DEAD;
- }
- write_unlock_irq(&tasklist_lock);
- release_thread(p); //释放线程资源,注意当前线程的task_struct在这里是不会释放的(引用计数不为0),因为目前还使用的它的进程上下文,schedule还需要他参与,它会在switch_to函数执行之后释放。
- call_rcu(&p->rcu, delayed_put_task_struct);
- p = leader;
- if (unlikely(zap_leader))
- goto repeat;
- }
点击(此处)折叠或打开
- bool do_notify_parent(struct task_struct *tsk, int sig)
- {
- struct siginfo info;
- unsigned long flags;
- struct sighand_struct *psig;
- bool autoreap = false;
- cputime_t utime, stime;
- if (sig != SIGCHLD) {
- if (tsk->parent_exec_id != tsk->parent->self_exec_id)
- sig = SIGCHLD;
- }
- // 初始化信号发送结构SIGCHLD
- info.si_signo = sig;
- info.si_errno = 0;
- rcu_read_lock();
- info.si_pid = task_pid_nr_ns(tsk, task_active_pid_ns(tsk->parent));
- info.si_uid = from_kuid_munged(task_cred_xxx(tsk->parent, user_ns),
- task_uid(tsk));
- rcu_read_unlock();
- task_cputime(tsk, &utime, &stime);
- info.si_utime = cputime_to_clock_t(utime + tsk->signal->utime);
- info.si_stime = cputime_to_clock_t(stime + tsk->signal->stime);
- info.si_status = tsk->exit_code & 0x7f;
- if (tsk->exit_code & 0x80)
- info.si_code = CLD_DUMPED;
- else if (tsk->exit_code & 0x7f)
- info.si_code = CLD_KILLED;
- else {
- info.si_code = CLD_EXITED;
- info.si_status = tsk->exit_code >> 8;
- }
- psig = tsk->parent->sighand;
- spin_lock_irqsave(&psig->siglock, flags);
- if (!tsk->ptrace && sig == SIGCHLD &&
- (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN ||
- (psig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDWAIT))) {
- // 父进程说:它要为儿子收尸,所以autoreap = true;后面会设置为僵尸进程,如果父进程一直不收,则一直处理僵尸状态
- autoreap = true;
- if (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN)
- sig = 0;
- }
- // 给父进程发一个SIGCHLD信号,通知收尸
- if (valid_signal(sig) && sig)
- __group_send_sig_info(sig, &info, tsk->parent);
- // 唤醒父进程
- __wake_up_parent(tsk, tsk->parent);
- spin_unlock_irqrestore(&psig->siglock, flags);
- return autoreap;
- }
文中提到的switch_to和schedule将会在系统调度章节详细说明;文中提到到mm_struct相关的,将在虚拟地址部分讲到。
通过《Linux内核之execve函数》和本章,我们知道进程的生命周期函数调用栈如下: