1sigaction的数据结构(signal action 信号处理)
在signal.h中typedef unsigned int sigset_t;
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
// sa_handler是对应某信号指定要采取的行动。可以用上面的SIG_DFL默认,或SIG_IGN来忽略该
//信号,也可以是指向处理该信号函数的一个指针
// sa_mask给出了对信号的屏蔽码,在信号程序执行时将阻塞对这些信号的处理。
// sa_flags指定改变信号处理过程的信号集。它是由37-39行的位标志定义的
// sa_restorer是恢复函数指针,由函数库Libc提供,用于清理用户态堆栈。参见signal. c
//另外,引起触发信号处理的信号也将被阻塞,除非使用了 SA_N0MASK标志
2进程结构
union task_union{ //c语言中的union是联合体
struct task_struct task ;
char stack[PAGE_SIZE] ;
}
这实际上是一个内存页,页的底部是进程控制块结构。其余部分是作为进程的内核态堆栈使用。
TASK_STRUCT
内核态堆栈
page
Page+4K
3task 数组
struct task_struct * task[NR_TASKS] = {&(init_task.task), };
这个数组中存储的是task_struct 结构的指针,但是实际上数组中的每一项都指着一块内存页。
4任务段数据
typedef struct desc_struct {
unsigned long a,b;
} desc_table[256];
extern desc_table idt,gdt;
//下面是数学协处理器使用的结构,主要用于保存进程切换时i387的执行状态信息。
struct i387_struct {
long cwd;// 控制字(Control word)
long swd;// 状态字(Status word)
long twd;// 标记字(Tag word)
long fip;//协处理器代码指针。
long fcs;//协处理器代码段寄存器。
long foo;//内存操作数的偏移位置。
long fos;//内存操作数的段值。
long st_space[20]; // 8个10字节的协处理器累加器
};
struct tss_struct
{
long back_link; //先前任务连接字段 /* 16 high bits zero back背 link纽带*/
long esp0;//特权级0的堆栈指针字段
long ss0; //特权级0的堆栈指针字段 /*高16位为零*/
long esp1;//特权级1的堆栈指针字段
long ss1; //特权级1的堆栈指针字段 /* 16 high bits zero */
long esp2;//特权级2的堆栈指针字段
long ss2;//特权级2的堆栈指针字段 /* 16 high bits zero */
long cr3; //CR3控制寄存器字段
long eip; //指令指针EIP字段
long eflags;//标志寄存器EFLAGS字段
long eax,ecx,edx,ebx;//通用寄存器字段。
long esp;//通用寄存器字段
long ebp;//通用寄存器字段
long esi;//通用寄存器字段
long edi;//通用寄存器字段
long es;//段选择符字段 /* 16 high bits zero */
long cs;//段选择符字段 /* 16 high bits zero */
long ss;//段选择符字段 /* 16 high bits zero */
long ds;//段选择符字段 /* 16 high bits zero */
long fs;//段选择符字段 /* 16 high bits zero */
long gs;//段选择符字段 /* 16 high bits zero */
long ldt; //LDT段选择符字段。 /* 16 high bits zero */
long trace_bitmap; //I/O位图基地址字段 /* bits: trace 0, bitmap 16-31 trace追踪bitmap位图*/
struct i387_struct i387;//数学协处理器使用的结构
};
/*1动态字段。当任务切换而被挂起时,处理器会更新动态字段的内容。这些字段包括:
1)通用寄存器字段。用于保存EAX、ECX、EDX、EBX、ESP、EBP、ESI和EDI寄存器的内容。
2)段选择符字段。用于保存ES、CS、SS、DS、FS和GS段寄存器的内容。
3)标志寄存器EFLAGS字段。在切换之前保存EFLAGSo
4)指令指针EIP字段。在切换之前保存EIP寄存器内容。
5)先前任务连接字段。含有前一个任务TSS段选择符(在调用、中断或异常激发的任务切换时更新)。
该字段(通常也称为后连接字段(Back link field))允许任务使用IRET指令切换到前一个任务。
2.静态字段。处理器会读取静态字段的内容,但通常不会改变它们。这些字段内容是在任务被创建时设置的。这些字段有:
1)LDT段选择符字段。含有任务的LDT段的选择符。
2)CR3控制寄存器字段。含有任务使用的页目录物理基地址。控制寄存器CR3通常也被称为页目
录基地址寄存器 PDBR (Page directory base register)
3)特权级0、1和2的堆栈指针字段。这些堆栈指针由堆栈段选择符(SSO、SS1和SS2)和栈中偏
移量指针(ESPO、ESPUD ESP2)组成。注意,对于指定的一个任务,这些字段的值是不变的。
因此,如果任务中发生堆栈切换,寄存器SS和ESP的内容将会改变
4)调试陷阱(Debug Trap) T标志字段。该字段位于字节0x64比特0处。当设置了该位时,处理器
切换到该任务的操作将产生一个调试异常
5)I/O位图基地址字段。该字段含有从TSS段开始处到I/O许可位图处的16位偏移值
如果使用了分页机制,那么在任务切换期间应该避免处理器操作的TSS段中(前104字节中)含有内
存页边界。如果TSS这部分包含内存页边界,那么该边界处两边的页面都必须同时并且连续存在于内存中。
另外,如果使用了分页机制,那么与原任务TSS和新任务TSS相关的页面,以及对应的描述符表表项应 该是可读写的。 */
5进程控制块
struct task_struct {undefined
/----------------------- these are hardcoded - don’t touch -----------------------/
long state; state状态; 国家 // 进程运行状态(-1不可运行,0可运行,>0以停止)
long counter; counter计数器// 任务运行时间片,递减到0是说明时间片用完
long priority; priority【扑ruai哦体】优先// 任务运行优先数,刚开始是counter=priority
long signal; // 任务的信号位图,信号值=偏移+1
struct sigaction sigaction[32]; //信号执行属性结构,对应信号将要执行的操作和标志信息 sigaction=signal+ action=信号+行动
long blocked; // 定义占空间32位的信号屏蔽码 block .v堵塞
/----------------------------------- various fields--------------------------------- /
int exit_code; //exit 英[ˈeksɪt]出口 退出 任务退出码,当任务结束时其父进程会读取
unsigned long start_code,end_code,end_data,brk,start_stack;
// start_code 代码段起始的线性地址
// end_code 代码段长度
// end_data 代码段长度+数据段长度
// brk 代码段长度+数据段长度+bss段长度
//BSS(Block Started by Symbol)通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。
//特点是:可读写的,在程序执行之前BSS段会自动清0。所以,未初始的全局变量在程序执行之前已经成0了
// start_stack 堆栈段起始线性地址
long pid,father,pgrp,session,leader;
// pid 进程号
// father 父进程号
// pgrp 父进程组号
// session 会话号 session .n学年
// leader 会话首领
unsigned short uid,euid,suid;
// uid 用户标id
// euid 有效用户id effective有效的
// suid 保存的用户id save保存的
unsigned short gid,egid,sgid;
// gid 组id
// egid 有效组id
// sgid 保存组id
long alarm; // 报警定时值
long utime,stime,cutime,cstime,start_time;
// utime 用户态运行时间 user time
// stime 内核态运行时间
// cutime 子进程用户态运行时间 child user time
// cstime 子进程内核态运行时间 child super time
// start_time 进程开始运行时刻
unsigned short used_math; // 标志,是否使用了387协处理器
/ ----------------------------------file system info-------------------------------- /
int tty; // 进程使用tty的子设备号,-1表示没有使用
unsigned short umask; //文件创建属性屏蔽码 mask v.掩饰; 掩藏;
struct m_inode * pwd; // 当前工作目录的i节点
struct m_inode * root; // 根目录的i节点 root根
struct m_inode * executable; // 可执行文件的i节点 executable【一科ZQ特bou】可执行的;
unsigned long close_on_exec; // 执行时关闭文件句柄位图标志 exec .n经理
struct file * filp[NR_OPEN]; // 进程使用的文件
/------------------ ldt for this task 0 - zero 1 - cs 2 - ds&ss -------------------/
struct desc_struct ldt[3]; // 本任务的ldt表,0-空,1-代码段,2-数据和堆栈段
/* ---------------------------------tss for this task ---------------------------------*/
struct tss_struct tss; // 本任务的tss段
};
6 linux进程结构
(1) 在linux中gdt中的每一项,都有两个表项,一个是ldt描述符,另一个是tss描述符。
(2) 在task数组中占有一项,每一项是一个物理页面,物理内存页面底端是进程控制块,内存页面的其余部分是内核态堆栈。
(3) task数组中的表项和gdt中的表项是一一对应的。 对于一个在task数组中的任务项是nr的任务来说,它的tss描述符在gdt中描述符 的位置是,gdtr + 3*8 + 16 * nr ,ldt描述符在gdt中的描述符的位置是 gdtr + 3 * 8 + 16 * nr + 8 。
(4) 对应于表项为nr的进程,它对应的页目录项是16 * nr --------16 * (nr + 1) 。
7 进程0
进程0是一个特殊的进程,它是所有进程的祖先进程,所有其他的进程都是复制进程0或者其后代进程产生的。 但是进程0不是。下面主要讲一下 进程0的创建顺序:
(1) 进程控制块和页目录页表的手动创建
以下就是一个任务的初始过程
#define INIT_TASK \
/* state etc */ { 0,15,15, \
/* signals */ 0,{{},},0, \
/* ec,brk */ 0,0,0,0,0,0, \
/* pid etc.. */ 0,-1,0,0,0, \
/* uid etc */ 0,0,0,0,0,0, \
/* alarm */ 0,0,0,0,0,0, \
/* math */ 0, \
/* fs info */ -1,0022,NULL,NULL,NULL,0, \
/* filp */ {NULL,}, \
{ \
{0,0}, \ // ldt第0项是空
/* ldt */ {0x9f,0xc0fa00}, \ //代码段长640K,基地0,G=1,D=1,DPL=3,P=1,TYPE=0x0a
{0x9f,0xc0f200}, \ //数据段长640K,基地0,G=1, D=1, DPL=3,P=1, TYPE=0x02
}, \
/*tss*/ {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\
// esp0 = PAGE_SIZE+(long)&init_task 内核态堆栈指针初始化为页面最后
// ss0 = 0x10 内核态堆栈的段选择符,指向系统数据段描述符,进程0的进程控制
// 块和内核态堆栈都在system模块中
// cr3 = (long)&pg_dir 页目录表,其实linux0.11所有进程共享一个页目录表
0,0,0,0,0,0,0,0, \
0,0,0x17,0x17,0x17,0x17,0x17,0x17, \
_LDT(0),0x80000000, \ // ldt表选择符指向gdt中的LDT0处
{} \
}, \
}
进程0的数据段基址为0,段限长为640KB ,代码段基址为0,段限长为640KB。任务0的数据段和代码段 和系统的代码段和数据段是重合的。
进程0的内核态堆栈和进程控制块都是位于系统模块内。
(2)main模块
在main模块中调用了sched_init()函数加载了 进程0的进程0的tss段描述符,ldt段描述符,并且加载TR寄存器,使它指向进程0的tss段,这时候进程0才完成了启动。
/*****************************************************************************/
/* 功能: 1. 初始化task数组和GDT(包括设置进程1的LDT和TSS) */
/* 2. 加载TR和IDTR寄存器 */
/* 3. 设置时钟中断门和系统调用中断门 */
/* 参数: (无) */
/* 返回: (无) */
/*****************************************************************************/
void sched_init(void)
{
int i;
struct desc_struct * p;
if (sizeof(struct sigaction) != 16)
panic("Struct sigaction MUST be 16 bytes");
// 在gdt中设置进程0的tss段描述符
set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
// 在gdt中设置进程0的ldt段描述符
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
// 下面的循环把gdt和task中其他的项清空
p = gdt+2+FIRST_TSS_ENTRY;
for(i=1;i<NR_TASKS;i++) {
task[i] = NULL;
p->a=p->b=0;
p++;
p->a=p->b=0;
p++;
}
/* Clear NT, so that we won't have troubles with that later on */
__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
ltr(0); // 把进程0的tss段加载到TR寄存器
lldt(0); // 把进程0的ldt段加载到IDTR寄存器。
// 这是将gdt中进程0的ldt描述符对应的选择符加载到TR中。CPU将
// 选择符加载到可见部分,将tss的基地址和段长等加载到不可见部分。
// TR寄存器只在这里明确加载一次,以后新任务ldt的加载是CPU根据
// TSS段中LDT字段自动加载。
// 初始化8253定时器
outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */
outb_p(LATCH & 0xff , 0x40); /* LSB */
outb(LATCH >> 8 , 0x40); /* MSB */
set_intr_gate(0x20,&timer_interrupt); // 设置时钟中断门
outb(inb_p(0x21)&~0x01,0x21);
set_system_gate(0x80,&system_call); // 设置系统调用中断门
}
(3)从内核态切换回用户态。
// 把进程0从内核态切换到用户态去执行,使用的方法是模拟中断调用返回
// 利用指令iret完成特权级的转变。
#define move_to_user_mode() \
__asm__ ("movl %%esp,%%eax\n\t" \ // 当前堆栈指针保存到eax中
"pushl $0x17\n\t" \ // 当前堆栈段选择符0x17入栈,它指向进程0的数据段描述符// 因为进程0的代码段、数据段、内核代码段、数据段4者重
// 合,所以它指向的仍然是内核模块区域。
"pushl %%eax\n\t" \ // 把当前堆栈指针入栈。这样模拟外层堆栈的SS:ESP。
// 由于进程0数据段选择符0x17对应的还是内核模块,和
// 内核数据段选择符0x10的差别仅在与对应描述符的dpl和
// 本身rpl的不同,所以外层堆栈指针指向的还是原来的堆栈
// 即user_stack
"pushfl\n\t" \ // eflags入栈
"pushl $0x0f\n\t" \ // 进程0代码段选择符入栈,模拟返回的CS
"pushl $1f\n\t" \ // 下面标号1的偏移地址入栈,模拟返回的EIP
// 也是由于4段重合,所以这里返回的CS对应的段的基地址与
// 内核代码段基地址一样,都是0,故将返回的CS:EIP就是下
// 面标号1处。
"iret\n" \ // 中断返回。由于当前CPL=0,将返回的CS的RPL=3,所以
// 不仅仅要改变CS,EIP,还要发生堆栈切换(但实际上堆栈
// 还是user_stack),同时CPL变成3。
"1:\tmovl $0x17,%%eax\n\t" \ // 把数据段寄存器的值设为进程0的数据段
"movw %%ax,%%ds\n\t" \
"movw %%ax,%%es\n\t" \
"movw %%ax,%%fs\n\t" \
"movw %%ax,%%gs" \
:::"ax")
6 用fork创建进程
除了进程0,所有其他的进程都是由fork()系统调用创建的,子进程是通过复制父进程的数据和代码而产生的。
创建结束之后,子进程与父进程的代码和数据共享,但是子进程有自己的进程控制块,内核堆栈和页表。
一个进程需要以下三中数据结构
(1) 进程控制块 task__struct 。
(2) gdt中的tss 和ldt描述符。
(3)页目录项和页表项。
所以fork系统调用的任务就是创建进程的上述三个部分。
sys_fork()函数分两步实现,第一步 首先调用,find_empty_process() 函数,第二步调用 copy_process()函数,复制进程。
_sys_fork:
// 第一步,调用find_empty_process()函数,找task[]中的空闲项。
// 找到后数组下标放在eax中。如果没找到直接跳转到ret指令。
call _find_empty_process
testl %eax,%eax
js 1f
push %gs // 中断时没有入栈的寄存器入栈,
// 作为copy_process() 函数的参数
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
// 第二步,调用copy_process() 函数复制进程。
call _copy_process
addl $20,%esp
1: ret
内存复制函数copy_mem
/*****************************************************************************/
/* 功能:设置新进程的LDT项(数据段描述符和代码段描述符)中的基地址部分 */
/* 并且复制父进程(也就是当前进程)的页目录和页表, */
/* 实现父子进程数据代码共享 */
/* 参数: nr 新进程任务数组下标 */
/* p 新进程的进程控制块 */
/* 返回: 0 (成功), -ENOMEM(出错) */
/*****************************************************************************/
int copy_mem(int nr,struct task_struct * p)
{
unsigned long old_data_base,new_data_base,data_limit;
unsigned long old_code_base,new_code_base,code_limit;
code_limit=get_limit(0x0f); // 取当前进程代码段长度
data_limit=get_limit(0x17); // 取当前进程数据段长度
old_code_base = get_base(current->ldt[1]); // 取当前进程代码段基地址,这是线性地址
old_data_base = get_base(current->ldt[2]); // 取当前进程数据段基地址,这是线性地址
// 0.11进程代码段和数据段基地址必须重合
if (old_data_base != old_code_base)
panic("We don't support separate I&D");
//0.11中数据段代码段的基地址是重合的,都是nr*64M(nr是task[]数组下标),所以
//数据段的长度肯定大于代码段长度。而且 copy_page_tables()传入的是data_limit,这
// 把代码和数据都包含进去了。
if (data_limit < code_limit)
panic("Bad data_limit");
// 新进程的代码段基地址 = 数据段基地址 = 64M*nr
new_data_base = new_code_base = nr * 0x4000000;
// 设置进程的起始线性地址
p->start_code = new_code_base;
// 设置新进程的ldt项。在copy_process()中完全复制父进程的ldt,所以
// 只需重新设置ldt的基地址字段,其他字段和父进程一样
set_base(p->ldt[1],new_code_base);
set_base(p->ldt[2],new_data_base);
// 把线性地址old_data_base处开始,一共data_limit个字节的内存对应的页目录、
// 页表复制到线性地址new_data_base。这里仅仅复制相关的页目录和页表,使它们
// 指向同一个物理页面,实现父子进程数据代码共享。
if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
free_page_tables(new_data_base,data_limit);
return -ENOMEM;
}
return 0;
}
复制进程
/*****************************************************************************/
/* 功能:复制进程,把当前进程current复制到task[nr] */
/* 参数:当前进程(current)内核堆栈的所有内容 */
/* 当前进程内核堆栈保存了所有寄存器的值,在程序中要把这些寄存器的值 */
/* 全部复制给子进程,从而给子进程创造和父进程一样的运行环境 */
/* 返回:子进程pid */
/*****************************************************************************/
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
long ebx,long ecx,long edx,
long fs,long es,long ds,
long eip,long cs,long eflags,long esp,long ss)
{
struct task_struct *p;
int i;
struct file *f;
// 在主内存区申请一页新的内存,用来放置子进程的task_struct和内核堆栈
// get_free_page()返回的是物理地址
p = (struct task_struct *) get_free_page();
if (!p)
return -EAGAIN;
// 设置task数组中相关项
task[nr] = p;
// 下面的赋值语句仅仅把父基础的task_struct部分全部复制给子进程
// 注意:仅仅复制task_struct部分,内核堆栈不复制,因此子程序的内核堆栈
// 是空的,这也是我们希望的
*p = *current; /* NOTE! this doesn't copy the supervisor stack */
// 下面的很多赋值语句修改子进程的task_struct中若干字段
// 这些字段跟父进程是有差别的
p->state = TASK_UNINTERRUPTIBLE; //子进程设为不可中断状态
p->pid = last_pid; // 设置子进程pid
p->father = current->pid; // 把当前进程pid舍为子进程的father
p->counter = p->priority; // 继承父亲的优先级
p->signal = 0;
p->alarm = 0;
p->leader = 0; /* process leadership doesn't inherit */
p->utime = p->stime = 0;
p->cutime = p->cstime = 0;
p->start_time = jiffies; // 子进程开始时间
p->tss.back_link = 0;
// 子进程的内核堆栈指针设置为task_struct所在页面的最高端
p->tss.esp0 = PAGE_SIZE + (long) p;
// 子进程的内核堆栈选择符为0x10,指向GDT中系统数据段。
// 注意 虽然子进程的内核堆栈位于内核system模块外,在主内存区,但是因为系统数据段
// 基地址为0,限长为16M,函概了所有物理内存,故子进程内核堆栈也位于系统数
// 段内。esp0要的是段内偏移,也是因为系统数据段基地址为0,物理地址
// PAGE_SIZE + (long) p 也是段内偏移。
p->tss.ss0 = 0x10;
// 把父进程系统调用返回地址赋给子进程当前运行的eip。这样当子进程被调度程序选中
// 后他从fork返回地址处开始执行。
p->tss.eip = eip;
p->tss.eflags = eflags;
// eax是函数返回值存放的地方,把子进程的eax设置为0,这样fork在子进程中返回的是0。
// 注意 子进程并没有执行fork()函数,子进程的系统堆栈没有进行过操作,当然不会有像
// 父进程那样的fork函数调用。但是当子进程开始运行时,就好像它从fork中返回。
p->tss.eax = 0;
p->tss.ecx = ecx;
p->tss.edx = edx;
p->tss.ebx = ebx;
p->tss.esp = esp; // 用户堆栈指针和父进程一样,子进程完全复制父进程的用户堆栈
p->tss.ebp = ebp;
p->tss.esi = esi;
p->tss.edi = edi;
p->tss.es = es & 0xffff;
p->tss.cs = cs & 0xffff;
p->tss.ss = ss & 0xffff;
p->tss.ds = ds & 0xffff;
p->tss.fs = fs & 0xffff;
p->tss.gs = gs & 0xffff;
// 设置子进程的ldt。从这里可以看到,task下标为nr的进程在GDT中的2项一定是
// _LDT(nr)和_TSS(nr)。task[]中的项和GDT中的2项一一对应。
p->tss.ldt = _LDT(nr);
p->tss.trace_bitmap = 0x80000000;
if (last_task_used_math == current)
__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
// 在copy_mem函数中设置子进程的代码段描述符,数据段描述符,并且复制父进程的
// 页目录、页表。实现和父进程代码数据的共享。
if (copy_mem(nr,p)) {
task[nr] = NULL;
free_page((long) p);
return -EAGAIN;
}
// 子进程继承父进程打开的文件,所以文件引用数目要加一
for (i=0; i<NR_OPEN;i++)
if (f=p->filp[i])
f->f_count++;
// 子进程继承父进程的工作目录、根目录和可执行文件,所以引用数目加一
if (current->pwd)
current->pwd->i_count++;
if (current->root)
current->root->i_count++;
if (current->executable)
current->executable->i_count++;
// GDT中对应位置(和nr对应)放入子进程的TSS描述符、LDT描述符
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
// 最后把子进程的状态设置为可运行状态,这样子进程可以被调度
p->state = TASK_RUNNING; /* do this last, just in case */
// 父进程返回子进程的pid
return last_pid;
}
8进程的结束
1、综述
进程结束的时候,需要关闭的资源主要有:
(1) 释放所有的物理页面。(子进程自己清除)
(2) 关闭所有打开的文件。(子进程自己清除)
(3) 清除task[] 中的相应的项。(父进程自己清除)
2、几点详述
(1) 子进程通过exit()清除前面两个选项,将自身的状态变为TASK_ZOMBIE 。(zombie 丧尸)
父进程通过调用waitpid() 将task[] 数组清空。
(2)一个进程的经过exit()之后,物理页表被清除 , 页表页目录项也被清除,但是它的进程控制块和内核堆栈还在。此时进程的状态变为TASK_ZOMBIE ,不会再被处理器处理。不被处理但是还占用着task数组中的一个表项,这 就成为了僵尸进程。
(3) 子进程调用了exit()函数之后,就通知父进程,父进程调用waitpid() 来清除 task数组中的表项。但是很有可能,父进程没有执行waitpid()操作,情况如下:
a) 父进程早于子进程执行exit()函数。
b) 子进程僵死,但是父进程没有调用waitpid()操作。
c) 父进程调用了waitpid(),但是因为某种愿意没有释放资源。
d)解决方法:
如果父进程无法释放资源,那么就让进程1来释放资源。
当一个父进程早于子进程exit()的时候,它把所有的子进程过继给父进程。
9POSIX .1要求的所有20个信号
//以下这些是 Linux 0.11内核中定义的信号。其中包括了 POSIX .1要求的所有20个信号。
# define SIGHUP 1 // Hang Up 一挂断控制终端或进程。Hang悬挂; 吊; 垂下;
# define SIGINT 2 // Interrupt 一来自键盘的中断。 Interrupt打断; 打扰; 插嘴
# define SIGQUIT 3 // Quit 一来自键盘的退出。Quit退出
# define SIGILL 4 //Illeagle 一非法指令ill eagle生病的老鹰;
# define SIGTRAP 5 // Trap 一跟踪断点。Trap陷阱
# define SIGABRT 6 // Abort 一异常结束。Abort中止; 流产,放弃;
# define SIGIOT 6 // IO Trap 一同上。
# define SIGUNUSED 7 // Unused 一没有使用。
# define SIGFPE 8 //FPE 一协处理器出错。
# definel SIGKILL 9 //Kill 一强迫进程终止。Kill 杀死; 弄死
# define SIGUSR1 10 //User1 一用户信号1,进程可使用。
# define SIGSEGV 11 // Segment Violation 一无效内存引用。Segment部分; 片; 段Violation侵犯;违背;亵渎
# define SIGUSR2 12 //User2 一用户信号2,进程可使用。
# define SIGPIPE 13 // Pipe 一管道写出错,无读者。Pipe管; 管道; 管子; 烟斗
# define SIGALRM 14 // Alarm 一实时定时器报警。 Alarm恐慌; 警报
# define SIGTERM 15 // Terminate 一进程终止。Terminate终止; (使)停止; 结束
# define SIGSTKFLT 16 //Stack Fault一栈出错(协处理器)。Stack 堆栈
# define SIGCHLD 17 // Child 一子进程停止或被终止。
# define SIGCONT 18 //Continue 一恢复进程继续执行。
# define SIGSTOP 19 // Stop 一停止进程的执行。
# define SIGTSTP 20 // TTY Stop 一 tty 发出停止进程,可忽略。
# define SIGTTIN 21 // TTY In 一后台进程请求输入。
# define SIGTTOU 22 // TTY Out 一后台进程请求输出。
//TTY 是 Teletype 或 Teletypewriter 的缩写,原来是指电传打字机,后来这种设备逐渐键盘和显示器取代。不管是电传打字机还是键盘显示器,都是作为计算机的终端设备存在的,所以 TTY 也泛指计算机的终端(terminal)设备。