0%

《linux 0.12内核完全剖析》--进程退出分析

笔记重点:
本来进程退出没有什么好谈的,但是进程退出都做了什么,可能会很模糊,比如进程退出会引发重新调度,比如资源的销毁对应进程创建的重新分配,进程的退出依赖于系统对信号的处理,所以应该先熟悉系统如何处理信号。

  • 信号处理是在什么时候进行的
  • 进程退出时要销毁哪些具体的资源

1. 从一个例子开始

1
2
3
4
5
6
7
8
9
10
11
12
void handle(int sig) {
printf("the signal is %d\n",sig);
(void) signal(SIGINT,SIG_DFL);//恢复默认处理函数
}

int main() {
(void) signal(SIGINT,handle); //设置SIGINT信号的信号处理函数
while(1) {
printf("signal test....\n");
sleep(1);
}
}

按ctrl-c时将会到handle函数处理,并将信号处理函数设置为SIG_DFL;

2. signal函数长什么样

上面调用的signal函数在libc库函数中,是对系统调用的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
extern void ___sig_restore();
void (*signal(int sig,__sighandler_t func))(int) {
void (*res)();
register int __fooebx __asm__ ("bx") = sig;
/*
eax 为系统调用号 __NR_signal
ebx 为 sig
ecx 为信号处理函数
edx 为__sig_restore函数
以下语句包裹__NR_signal系统调用
*/
__asm__("int $0x80":"=a" (res):
"0"(__NR_signal),"r"(__fooebx),"c"(func),"d"((long)__sig_restore))
return res;
}

3. 信号处理的分析过程

  • 信号的处理是在每次系统调用时或者时钟中断时
  • do_signal函数是内核在系统调用时对信号的预处理
3.1. 每次系统调用完成后的处理 sys_calls.s 第107行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ret_from_sys_call:
...
movl signal(%eax),%ebx #取信号位图
movl blocked(%eax),%ecx #取屏蔽信号位图
notl %ecx
andl %ebx,%ecx
bsfl %ecx,%ecx #从位0开始扫描有1的位
je 3f #没有则退出
btrl %ecx,%ebx
movl %ebx,signal(%eax)
incl %ecx
pushl %ecx #信号值入栈
call _do_signal #!!!调用do_signal函数
popl %ecx
testl %eax, %eax
jne 2b # see if we need to switch tasks, or do more signals
3: popl %eax
popl %ebx
popl %ecx
popl %edx
addl $4, %esp # skip orig_eax
pop %fs
pop %es
pop %ds
iret
3.2. do_signal函数将信号处理句柄插入到用户程序堆栈

系统调用返回时有信号处理时,返回路径是执行信号的处理,没有则直接返回!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int do_signal(long signr,long eax,long ebx, long ecx, long edx, long orig_eax,
long fs, long es, long ds,
long eip, long cs, long eflags,
unsigned long * esp, long ss)
{
unsigned long sa_handler;
long old_eip=eip;
struct sigaction * sa = current->sigaction + signr - 1;
int longs;
......
sa_handler = (unsigned long) sa->sa_handler;
if (sa_handler==1)
return(1); /* Ignore, see if there are more signals... */
//信号的默认处理
//代码已略,基本上都是结束当前进程
......
/*
* OK, we're invoking a handler
*/
if (sa->sa_flags & SA_ONESHOT)
sa->sa_handler = NULL;
//以下这段就是把信号处理函数插入到用户堆栈中
//插入用户堆栈的目的就是系统调用返回后,调用用户自定义的信号处理函数
*(&eip) = sa_handler;
longs = (sa->sa_flags & SA_NOMASK)?7:8;
*(&esp) -= longs;
verify_area(esp,longs*4);
tmp_esp=esp;
//处理完用户的信号处理函数后的清理函数 sa->sa_restorer,也就是上面第2点的__sig_restore函数
put_fs_long((long) sa->sa_restorer,tmp_esp++);
put_fs_long(signr,tmp_esp++);
if (!(sa->sa_flags & SA_NOMASK))
put_fs_long(current->blocked,tmp_esp++);
put_fs_long(eax,tmp_esp++);
put_fs_long(ecx,tmp_esp++);
put_fs_long(edx,tmp_esp++);
put_fs_long(eflags,tmp_esp++);
put_fs_long(old_eip,tmp_esp++);
current->blocked |= sa->sa_mask;
return(0); /* Continue, execute handler */
}
3.3. __sig_restore函数做了什么

libc-2.2.2中实现了一下函数,信号处理函数完成以后,返回用户程序做一些处理,因为信号处理函数是插入的!

1
2
3
4
5
6
7
8
.global ____sig_restore
____sig_restore:
addl $4,%esp #丢弃信号值
popl %eax #恢复系统调用返回值
popl %ecx
popl %edx
popfl #恢复用户程序的标志寄存器
ret

4. 进程退出都做了什么

  • 释放占用的内核中的内存(task_struct)和分配的物理内存
  • 当前进程的结束(状态的改变) 对当前进程的父进程和当前进程的子进程,以及兄弟进程的影响
  • 内核重新调度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
volatile void do_exit(long code)
{
struct task_struct *p;
int i;
//释放分配的物理内存,0x0f和0x17都是选择子
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
free_page_tables(get_base(current->ldt[2]),get_limit(0x17));

......
//状态发生变化
current->state = TASK_ZOMBIE;
current->exit_code = code;
......

/*
* Check to see if any process groups have become orphaned
* as a result of our exiting, and if they have any stopped
* jobs, send them a SIGUP and then a SIGCONT. (POSIX 3.2.2.2)
*
* Case i: Our father is in a different pgrp than we are
* and we were the only connection outside, so our pgrp
* is about to become orphaned.
*/
if ((current->p_pptr->pgrp != current->pgrp) &&
(current->p_pptr->session == current->session) &&
is_orphaned_pgrp(current->pgrp) &&
has_stopped_jobs(current->pgrp)) {
kill_pg(current->pgrp,SIGHUP,1);
kill_pg(current->pgrp,SIGCONT,1);
}
/* Let father know we died */
//告诉父进程,当前进程退出
current->p_pptr->signal |= (1<<(SIGCHLD-1));

/*
* This loop does two things:
*
* A. Make init inherit all the child processes
* B. Check to see if any process groups have become orphaned
* as a result of our exiting, and if they have any stopped
* jons, send them a SIGUP and then a SIGCONT. (POSIX 3.2.2.2)
*/
if (p = current->p_cptr) {
while (1) {
//让所有的子进程都继承在init进程task[1]
p->p_pptr = task[1];
if (p->state == TASK_ZOMBIE)
task[1]->signal |= (1<<(SIGCHLD-1));
/*
* process group orphan check
* Case ii: Our child is in a different pgrp
* than we are, and it was the only connection
* outside, so the child pgrp is now orphaned.
*/
if ((p->pgrp != current->pgrp) &&
(p->session == current->session) &&
is_orphaned_pgrp(p->pgrp) &&
has_stopped_jobs(p->pgrp)) {
kill_pg(p->pgrp,SIGHUP,1);
kill_pg(p->pgrp,SIGCONT,1);
}

......
}
}

......
//重新调度
schedule();
}

5. 任务的状态:

  • 就绪两种 : 内核运行和用户运行
  • 睡眠两种: 不可中断和可中断,不可中断的进程要显示唤醒,可中断的可由调度程序唤醒
  • 退出两种: 暂停和僵死