孤儿进程和僵尸进程
如果父进程先退出,子进程还没退出那么子进程的父进程将变为init进程。(注:任何一个进程都必须有父进程)
如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这个时候子进程就成为僵进程。
孤儿进程
如果父亲进程先结束,子进程会托孤给1号进程
父进程注册SIGCHLD信号,然后在回调函数里while (waitpid(0, NULL, WNOHANG) > 0); 注意这个分号不能少.
避免僵尸进程的另一种方法
#include <signal.h>
signal(SIGCHLD, SIG_IGN); 告诉内核,本进程要忽略SIGCHLD,请内核大哥帮忙处理.
在SIGCHLD信号被显式忽略的情况下,2.6内核子进程将直接退出并释放资源,等父进程调用waitpid时,发现对应的pid进程已不存在,因而返回-1错误,errno为10(No child processes)。
从某位高手的博客里看到了这个解释。大概就是因为显示忽略SIGCHLD信号时,内核会处理子进程的退出状态,再调用waitpid的时候,就找不到子进程了。不显示忽略的时候,内核不会对子进程的退出做处理,所以还是能找到的。至于设置了信号处理函数之后为什么还是能正常返回?我推测应该是进程同步问题。信号处理函数对这个信号的处理是异步的,所以等到空闲时,父进程才会调用这个函数来处理信号,在这之前,waitpid就已经发挥作用,所以正常返回。由于初学,所以也不太懂,还请高手指正阿!
1、wait和waitpid出现的原因 |
SIGCHLD 当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止) 子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。 父进程查询子进程的退出状态可以用wait/waitpid函数 |
Wait获取status后检测处理
宏定义 描述
WIFEXITED(status) 如果子进程正常结束,返回一个非零值
WEXITSTATUS(status) 如果WIFEXITED非零,返回子进程退出码
WIFSIGNALED(status) 子进程因为捕获信号而终止,返回非零值
WTERMSIG(status) 如果WIFSIGNALED非零,返回信号代码
WIFSTOPPED(status) 如果子进程被暂停,返回一个非零值
WSTOPSIG(status) 如果WIFSTOPPED非零,返回一个信号代码
用来等待某个特定进程的结束
waitpid(pid_t pid, int *status,int options)
对于waitpid的p I d参数的解释与其值有关:
pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。
pid > 0 等待其进程I D与p I d相等的子进程。
pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用者进程同在一个组的进程。
pid < -1 等待其组I D等于p I d的绝对值的任一子进程。
Wait和waitpid区别和联系
在一个子进程终止前,wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
实际上wait函数是waitpid函数的一个特例。
abort() //会发出一个6号信号
会话期:是一个或者多个进程组的集合,通常一个会话期开始与用户登录,终止于用户退出。在此期间,该用户运行的所有进程都属于这个会话期。
中断
中断是系统对于异步事件的响应()
中断信号
中断源
现场信息
中断处理程序
中断向量表
异步事件的响应:进程执行代码的过程中可以随时被打断,然后去执行异常处理程序
生活中的中断和计算机系统中的中断
4)中断的其他概念
中断向量表保存了中断处理程序的入口地址。
中断个数固定,操作系统启动时初始化中断向量表。
中断有优先级(有人敲门,有人打电话,有优先级)
中断可以屏蔽(张三可以屏蔽电话)。
信号名称 描述
SIGABRT 进程停止运行 6
SIGALRM 警告钟
SIGFPE 算述运算例外
SIGHUP 系统挂断
SIGILL 非法指令
SIGINT 终端中断 2
SIGKILL 停止进程(此信号不能被忽略或捕获)
SIGPIPE 向没有读者的管道写入数据
SIGSEGV 无效内存段访问
SIGQUIT 终端退出3
SIGTERM 终止
SIGUSR1 用户定义信号1
SIGUSR2 用户定义信号2
SIGCHLD 子进程已经停止或退出
SIGCONT 如果被停止则继续执行
SIGSTOP 停止执行
SIGTSTP 终端停止信号
SIGTOUT 后台进程请求进行写操作
SIGTTIN 后台进程请求进行读操作
进程对信号的三种相应
忽略信号
不采取任何操作、有两个信号不能被忽略:SIGKILL(9号信号)和SIGSTOP。
思考1:为什么进程不能忽略SIGKILL、SIGSTOP信号。(如果应用程序可以忽略这2个信号,系统管理无法杀死、暂停进程,无法对系统进行管理。)。SIGKILL(9号信号)和SIGSTOP信号是不能被捕获的。
捕获并处理信号
内核中断正在执行的代码,转去执行先前注册过的处理程序。
执行默认操作
默认操作通常是终止进程,这取决于被发送的信号。
signal信号安装函数
函数原型:__sighandler_t signal(intsignum, __sighandler_t handler);
返回值,如果成功则返回信号以前的行为
不可靠信号PK可靠信号
不可靠信号是前32个信号,不支持排队.在快速发送时可能会造成丢失,因为在进程PCB控制的信号未决信号集,是不能排队的,多个同种信号的到来,只会有一个同种信号在未决集.
可靠信号
随着时间的发展,实践证明,有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种unix版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。
sleep函数几点说明
1)sleep函数作用,让进程睡眠。
2)能被信号打断,然后处理信号函数以后,就不再睡眠了。直接向下执行代码
3)sleep函数的返回值,是剩余的秒数
raise
raise
给自己发送信号。raise(sig)等价于kill(getpid(), sig);
killpg
给进程组发送信号。killpg(pgrp, sig)等价于kill(-pgrp, sig);
sigqueue
给进程发送信号,支持排队,可以附带信息。
pause()函数
将进程置为可中断睡眠状态。然后它调用内核函数schedule(),使linux进程调度器找到另一个进程来运行。
pause使调用者进程挂起,直到一个信号被捕获
alarm函数,设置一个闹钟延迟发送信号
告诉linux内核n秒中以后,发送SIGALRM信号;;
可重入函数概念
为了增强程序的稳定性,在信号处理函数中应使用可重入函数。
所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为不安全函数。
满足下列条件的函数多数是不可再入的:(1)使用静态的数据结构,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函数实现时,调用了malloc()或者free()函数;(3)实现时使用了标准I/O函数的
信号在内核中的表示
执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
画图
当一个信号发送给一个进程时,进程控制块pcb控制着这个信号的走向,未决集(只可读,不可写),阻塞集(可读可写)都是64位数,因为有64个信号,每个位对应一个信号,一个信号要通过这两个集才能抵达
未决集初始状态全为0,信号首先进入到未决集,未决集的相应位变成1,然后查看阻塞位,如果阻塞位是1,那么信号无法通过,就一直在未决集,此时叫做未决态,一旦阻塞位变成0了,信号马上通过,未决集还没有变成0,
信号最后由程序决定如何处理(忽略,捕捉,默认行为),处理时未决集变成0,可以接收新的信号.
在信号处理函数中对某个信号进行解除阻塞时,则只是将pending位清0
信号集操作函数(状态字表示)
#include <signal.h>
intsigemptyset(sigset_t *set);把信号集清空 64bit/8=8个字节 sigset_t是一个64位的数
intsigfillset(sigset_t *set);把信号集全填写成1
intsigaddset(sigset_t *set, intsigno);根据signo,把信号集中的对应为置成1
intsigdelset(sigset_t *set, intsigno);根据signo,把信号集中的对应为置成0
intsigismember(constsigset_t *set, intsigno);//判断signo是否在信号集中
sigprocmask读取或更改进程的信号屏蔽状态字(block)
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
功能:读取或更改进程的信号屏蔽字。
返回值:若成功则为0,若出错则为-1
sigpending获取信号未决状态字(pending)信息
#include <signal.h>
int sigpending(sigset_t *set);
sigaction函数
信号注册函数,与signal一样,只不过功能更加强大.
int sigaction(int signum,const struct sigaction *act,const struct sigaction *old);
sigaction结构体
第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等
structsigaction {
void (*sa_handler)(int); //信号处理程序不接受额外数据
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序能接受额外数据,和sigqueue配合使用
sigset_t sa_mask; //信号处理函数执行的过程中阻塞哪些信号 sa_mask类型与未注册的信号集一样.但不需要sigprocmask
int sa_flags; //影响信号的行为SA_SIGINFO表示能接受数据
void (*sa_restorer)(void); //废弃
};
注意1:回调函数句柄sa_handler、sa_sigaction只能任选其一。
struct sigaction act; union sigval mysigval; mysigval.sival_int = 111; sigemptyset(&act.sa_mask); //信号处理过程中不阻塞其它信号, act.sa_flags = SA_SIGINFO; //表示发送数据外加新的内容 act.sa_sigaction = msigaction; //回调函数赋值 sigaction(SIGINT, &act, NULL); //注册信号 sigqueue(getpid(), SIGINT, mysigval); //发送信号
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。