什么是信号?
信号就是软中断
什么是软中断?
软中断就是软件模拟的中断
什么是中断?
中断服务程序。
信号是进程的异步通信机制。
系统提供的信号。如何查看?kill -l
一般来说,信号有64个信号。kill -信号的编号 pid
信号的产生、信号阻塞、信号未决、进程对信号的捕获、信号处理程序。
进程对信号的默认处理动作就是终止当前进程。
用户可以根据自己的需求来设置进程的对信号的处理。
SIG_DFL 缺省的
SIG_IGN 忽略
用户自定义信号处理函数
需要向进程注册一个函数,当信号到来的时候,使用注册的信号处理函数来处理信号到达。
signal(3)
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:为信号注册信号处理函数
参数:
signum:信号的名字或信号的编号
handler:
SIG_DFL 缺省的
SIG_IGN 忽略
用户自定义信号处理函数
返回值:
SIG_ERR 错误
返回原来的信号处理函数
举例验证使用signal函数为进程注册处理函数
结果:
5.信号处理流程
a)进程正常运行的时候,收到信号。
b)切换到内核态。
c)进程从内核态切回到用户态之前,先检查是否有信号到达。
如果有信号到达,调用用户态的信号处理函数,信号处理完毕调用sigreturn切回到内核态。继续执行
如果没有信号到达,切回到用户态继续进程的执行。
信号的产生:
1.硬件产生信号
例如:ctrl + c ctrl+\
2.使用linux命令发送信号
kill -信号编号 pid
3.使用函数给进程法发送信号
kill(2)
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:发送信号给进程
参数:
pid:正数 进程的pid 给这个进程发送信号
sig:指定信号编号
返回值:
0 成功
-1 错误 errno被设置
举例验证kill(2)的使用
发送程序:
接收程序:mykill.c
结果:
raise (3)
#include <signal.h>
int raise(int sig);
功能:给当前进程发送信号
参数:
sig:要发送的信号编号
返回值:
0 成功
非0 失败
举例验证:
alarm(2)
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:设置一个传递信号的闹钟(设定时间内,程序的运行)
参数:
seconds:设置闹钟的秒数
返回值:保留的秒数
举例验证
信号阻塞和未决信号
进程可以设置对某个信号的阻塞(屏蔽)
#include <signal.h>
int sigemptyset(sigset_t *set);//将信号清空
int sigfillset(sigset_t *set);将信号置满信号集
int sigaddset(sigset_t *set, int signum);//将信号signum添加到set集合里
int sigdelset(sigset_t *set, int signum);//将信号signum从信号set的集合里清除
int sigismember(const sigset_t *set, int signum);//判断信号signum是不是集合set的有效成员
希望设置进程对2号信号阻塞,需要如何处置阻塞信号集。
1.sigset_t block_set
2.将block_set集合里所有的成员清空
3.将2号信号添加到block_set集合中
4.将block_set设置为这个进程的阻塞信号集
完成第四步需要使用函数sigprocmask(2)
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:检测或改变阻塞信号
参数:
how:
SIG_BLOCK 当前信号集和set指定信号集的并集
SIG_UNBLOCK 将set集合里的成员从当前进程阻塞信号集中移除
SIG_SETMASK 将set指定为当前进程的阻塞信号。
set 指定的新的信号集
oldset: 之前的信号集保存到oldset中。
返回值:
0 成功
-1 错误
1.解除对应信号阻塞的时候是什么情况?
可靠信号:没有信号丢失的信号成为可靠信号 34~ 64
不可靠信号:有信号丢失的情况,这种信号为不可靠信号 1~31
2.在信号阻塞期间,可以观察未决信号。
如何查看未决信号。使用sigpending查看
#include <signal.h>
int sigpending(sigset_t *set);
功能:检测未决信号
参数:
set:值结果参数,用于返回进程的未决信号的掩码。
返回值:
0 成功
-1 错误
举例验证,使用sigpending检测当前进程的未决信号。
pause(2)的使用
#include <unistd.h>
int pause(void);
功能:等待一个信号
返回值:
-1 错误 errno 被设置为EINTR
举例验证pause(2)的使用
使用alarm(2)和pause(2)完成sleep的功能
让程序暂定
信号传送处理过程
1.用户输入命令,在bash下启动一个前台作业。
2.用户按下ctrl+c建,这个键盘输入产生一个硬件中断。
3.CPU正在执行这个进程的代码,则该进程的用户空间代码暂停执行。CPU从用户态切换到内核态处理硬件中断。
4.终端驱动程序将ctrl+C 解释成一个SIGNT信号,记录在改进程的PCB中。
5.当某个时刻,进程从内核态切换回用户态的时候,首先处理PCB中记录的信号,如果有未处理信号,找到信号对应的信号处理程序进行处理。
6.信号处理完毕,调用sigreturn(2),j继续返回到进程的内核态,再次循环到第五步。
有信号到来的时候,进程从用户态切换到内核态。信号会被记录到PCB中,当进程切换到用户态时,检测PCB是否有信号产生,当有信号产生时执行信号的处理程序。执行完毕后,回到内核态,继续继续查找。
可重入函数
函数使用到的变量的空间全部分配在栈帧中,这样的函数被称为可重入函数,否则为不可重入函数。
信号的处理函数尽量保证为可重入函数。
举例验证可重入函数的重要性:
编写代码验证信号处理函数的继承
验证子进程是否继承父进程的信号处理函数。
子进程继承了父进程的信号处理函数。信号属于进程的资源,在进程的PCB中有信号的记录。
setitimer实现定时器
系统计时器
运行一个进程的时候,进程所消耗的时间包括三部分:
用户时间:进程消耗在用户态的时间
内核时间:进程消耗在内核态的时间
睡眠时间:进程消耗在等待I/O 睡眠等不被调度的时间
内核为每个进程维护三个计时器
真实计时器:统计进程的执行时间
虚拟计时器:统计进程的用户时间
实用计时器:统计进程的用户时间和内核时间之和
执行时间=用户时间+内核时间+睡眠时间
这三个计时器除了统计进程的各种时间以外,还可以按照各自的计时规则,以定时器的方式工作,向进程周期性的发送不同信号。
SIGALRM 真实定时器
SIGVTALRM 虚拟定时器
SIGPROF 实用定时器
通过使用setitmer(2)设置,启动,关闭定时器。
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
功能:设置一个间隔时间的定时器
参数:
which:
ITIMER_REAL
ITIMER_VIRTUAL
ITIMER_PROF
new_value:定时器新值
old_value:定时器原来的值
返回值:
0 成功
-1 错误 errno被设置
补充:
举例验证 setitimer(2)的使用
前台作业和后台作业
什么是作业?
一个作业可以包含多个进程
将前台运行的作业切换到后台停止。ctrl + z
查看后台作业 jobs
将后台作业切换到前台。使用fg % 作业号
让停止的作业在后台运行 使用bg
前台作业可以使用快捷键发送信号杀死,后台作业不可以。
启动进程的时候,可以让作业直接到后台运行