点击(此处)折叠或打开
-
#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函数》和本章,我们知道进程的生命周期函数调用栈如下:
