# 信号
信号
本质是一个整数
,不同的信号对应不同的值
,由于信号的结构简单,不能携带大量信息量,在系统中的优先级非常高
# 信号编号
SIGHUP
: 用户退出shell
时,由 shell 启动的所有进程都收到此信号
SIGINT
: 用户按下Ctrl + C
组合键,用户终端向所有正在运行
的由该终端启动的程序发出此信号
// 查看man文档的信号描述 | |
man 7 signal |
# 信号状态
产生
:输入,函数调用
,执行设立了指令
未决
:信号残生,但此信号还没有被处理掉
抵达:
信号被处理了
# 信号函数
#inlcude <signal.h> | |
//给某个进程发送信号 | |
int kill(pid_t pid,int sig); | |
//pid 进程ID | |
//sig 要发送的信号 | |
//杀死自己 | |
kill(getpid(),9); | |
// 子进程杀死自己父进程 | |
kill(getppid(),10); | |
//raise给当前进程发送指定的信号 | |
int raise(int sig); //参数就是要给当前进程发送信号 | |
// abort给当前进程发送一个固定信号(SIGABRT) | |
#include <stdlib.h> | |
void abort(void); | |
//定时器 | |
unsigned int alarm(unsigned int seconds); | |
// 倒计时seconds秒,倒计时完成发送一个信号SIGALRM,当前进程收到这个信号,默认处理中断当前进程 | |
// 大于0 表示倒计时剩余多少秒,返回0表示倒计时完成 |
# 计算一秒数数
文件
IO
操作需要进行用户区到核心区
的切换,处理方式不同,两者切换频率
不同。对文件IO
操作进行优化是可以提供程序
的执行效率。
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <string.h> | |
int main() | |
{ | |
// 1. 设置一个定时器,定时 1s | |
alarm(1); // 1s 之后会发出一个信号,这个信号将中断当前进程 | |
int i = 0; | |
while(1) | |
{ | |
printf("输出信号 : %d\n", i++); | |
} | |
return 0; | |
} | |
// 运行数据 直接通过终端数据 | |
time ./time | |
// 将数据重定向到磁盘文件中,用户实际时间增长 | |
time ./time > a.txt |
# 周期性信号函数
setitimer()
函数可以进行周期性定时,每
触发一次
就会发射出
一个信号
#include <sys/time.h> | |
struct itimerval { | |
struct timeval it_interval; /* 时间间隔 */ | |
struct timeval it_value; /* 第一次触发定时器的时长 */ | |
}; | |
// 举例: | |
// 早晨 5 点起床,第一次闹钟响起时可能起不来,之后每隔 5 分钟再响一次 | |
// - it_value: 当前设置闹钟的时间点 到 明天早晨 7 点 对应的总秒数 | |
// - it_interval: 闹钟第一次响过之后,每隔 5 分钟响一次 | |
// 这个结构体表示的是一个时间段: tv_sec + tv_usec | |
struct timeval { | |
time_t tv_sec; /* 秒 */ | |
suseconds_t tv_usec; /* 微妙 */ | |
}; | |
int setitimer(int which, const struct itimerval *new_value, | |
struct itimerval *old_value); | |
//which 定时器使用什么样的计时法 | |
* ITIMER_REAL:自然计数法,发出的信号为SIGALRM,自然计时法时间=用户区+内核+消耗的时间(从进程的用户区到内核区切换的总时间) | |
* ITIMER_VIRTUAL:只计算用户区运行使用的时间,发射的信号为SIGVTALRM | |
* ITIMER_PROF: 只计算内核运行使用的时间,发出的信号为SIGPROF | |
//new_value : 定时器设置的定时信息,传入参数 | |
//old_value : 上一次给定时器设置的定时信息,传出参数,如果不需要指定为 NULL |
# 信号集
信号
的未决
是一种状态,指的是信号的产生到信号被处理前
的这一段时间
- 信号的
阻塞
是一个开关动作
,值得使阻止信号被处理,但不是阻止信号产生
。信号的阻塞就是让系统暂时保留
信号待以后发送。
阻塞信号集和未决信号集在内核中
结构相同
,都是整型数组
,一共128字节
,1024
个标志位,前31
个标志位。默认
没有信号被阻塞
,信号标识为为0
,阻塞
状态为1
.如果信号
被阻塞
,不能处理则标志位
被设置为1
阻塞
被解除
,未决信号马上被处理
,信号标志位为0
# 信号集函数
# sigprocmask
读写信号集
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset); | |
// 参数 | |
// how | |
* SIG_BLOCK:将参数set集合中数据添加到阻塞信号集中 | |
* SIG_UNBLOCK:将参数set集合中在阻塞信号中解除阻塞 | |
* SIG_SETMASK : 使参数set集合中的数据覆盖内核的阻塞信号集数据 | |
//oldset: 通过此参数将之前的阻塞数据集传出,若不需要指定 NULL | |
// 返回值:调用成功返回 0,调用失败返回 - 1 |
# sigset_t
s 初始化
在程序中读写
sigset_t
类型的变量,阻塞信号和未决信号集都存储在sigset_t
类型的变量中
,这个变量对应一块内存
,则是信号集
和未决信号集
,对应内存1024bit = 128
字节
// 将 set 集合中所有的标志位设置为 0 | |
int sigemptyset(sigset_t *set); | |
// 将 set 集合中所有的标志为设置为 1 | |
int sigfillset(sigset_t *set); | |
// 将 set 集合中的某一个信号 (signum) 对应的标志位设置为 1 | |
int sigaddset(sigset_t *set,int signum); | |
// 将 set 集合中某一个信号 (signum) 对应的标志位设置为 0`` | |
int sigdelset(sigset_t *set,int signum); | |
// 判断某个信号在集合中对应的标志位是 0 还是 1,返回对应标志位 | |
int sigismember(const sigset_t *set,int signum); | |
// 读内核未决信号集 | |
int sigpending (sigset_t *set); |
# 信号捕捉
程序中每个信号产生之后都会有对应的
默认处理动作
。程序中信号捕捉
,需要提前注册
,告诉应用程序信号产生之后做什么样的处理。
# signal
可以
捕捉
进程中产生的信号
,并且修改捕
捉到的函数行为
,信号自定义动作时回调函数,内核通过signal()
得到这个回调函数地址,在信号产生之后会被内核调用,信号产生之前,提前注册
#include <signal.h> | |
sighandler_t signal(int signum,sighandler_t handler); | |
//sighandler_t 需要捕捉的信号 | |
//handler 信号捕捉到之后的处理动作,函数指针 | |
typedef void(*sighandler_t)(int); |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <sys/time.h> | |
#include <signal.h> | |
// 定时器信号的处理动作 | |
void doing(int arg) | |
{ | |
printf("当前捕捉到的信号是: %d\n", arg); | |
// 打印当前的时间 | |
} | |
int main() | |
{ | |
// 注册要捕捉哪一个信号,执行什么样的处理动作 | |
signal(SIGALRM, doing); | |
// 1. 调用定时器函数设置定时器函数 | |
struct itimerval newact; | |
// 3s 之后发出第一个定时器信号,之后每隔 1s 发出一个定时器信号 | |
newact.it_value.tv_sec = 3; | |
newact.it_value.tv_usec = 0; | |
newact.it_interval.tv_sec = 1; | |
newact.it_interval.tv_usec = 0; | |
// 这个函数也不是阻塞函数,函数调用成功,倒计时开始 | |
// 倒计时过程中程序是继续运行的 | |
setitimer(ITIMER_REAL, &newact, NULL); | |
// 编写一个业务处理,阻止当前进程自己结束,让当前进程被发出的信号杀死 | |
while(1) | |
{ | |
sleep(1000000); | |
} | |
return 0; | |
} |
# sigaction
同
signal
信号捕捉
#include <signal.h> | |
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); | |
struct sigaction { | |
void (*sa_handler)(int); // 指向一个函数 (回调函数) | |
void (*sa_sigaction)(int, siginfo_t *, void *); | |
sigset_t sa_mask; // 初始化为空即可,处理函数执行期间不屏蔽任何信号 | |
int sa_flags; // 0 | |
void (*sa_restorer)(void); // 不用 | |
}; | |
//sa_handler: 函数指针,指向捕捉到信号的处理动作 | |
//sa_sigaction: 上同 | |
//sa_mask: 在信号处理函数执行期间,临时屏蔽某些信号 | |
//sa_flags: 使用那个指针函数 0 使用 sa_handler, SA_SIGINFO 使用 sa_sigaction 使用信号传递数据 == 进程间通信 |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <signal.h> | |
// 信号的处理动作 | |
void callback(int num) | |
{ | |
printf("捕捉信号"); | |
printf("当前捕捉的信号: %d\n", num); | |
} | |
int main() | |
{ | |
// 1. 初始化信号集 | |
sigset_t myset; | |
sigemptyset(&myset); | |
// 设置阻塞的信号 | |
sigaddset(&myset, SIGINT); // 2 | |
sigaddset(&myset, SIGQUIT); // 3 | |
sigaddset(&myset, SIGKILL); // 9 测试不能被阻塞 | |
// 当阻塞的信号被解除阻塞,该信号就可以被捕捉到了 | |
// 如果信号被捕捉到之后,马上就被处理掉了 --> 递达状态 | |
struct sigaction act; | |
act.sa_handler = callback; // 回调函数 | |
act.sa_flags = 0; // 0 对应 sa_handler | |
// 将 set 集合中所有标志位设置为 0 | |
sigemptyset(&act.sa_mask); // 初始化为空,不屏蔽任何信号 | |
// 设置信号捕捉 | |
sigaction(SIGINT, &act, NULL); //CTRL +C | |
// 和 sigint 的处理动作相同 | |
sigaction(SIGQUIT, &act, NULL); | |
sigaction(SIGKILL, &act, NULL); | |
// 2. 将初始化的信号集中的数据设置给内核 | |
sigset_t old; | |
// 使用此函数修改内核中阻塞信号集, | |
sigprocmask(SIG_BLOCK, &myset, &old); | |
// 3. 让进程一直运行,在当前进程中产生对应的信号 | |
int i = 0; | |
while(1) | |
{ | |
// 4. 读内核的未决信号集 | |
sigset_t curset; | |
// 读取集合中哪个信号是未决状态 | |
sigpending(&curset); | |
// 遍历这个信号集 | |
for(int i=1; i<32; ++i) | |
{ | |
// 判断某个信号在集合中对应的标志位是 0 还是 1, | |
int ret = sigismember(&curset, i); | |
printf("%d", ret); | |
} | |
printf("\n"); | |
sleep(1); | |
i++; | |
if(i==10) | |
{ | |
// 解除阻塞,重新设置阻塞信号集 | |
//sigprocmask(SIG_UNBLOCK, &myset, NULL); | |
sigprocmask(SIG_SETMASK, &old, NULL); | |
} | |
} | |
return 0; | |
} |
# SIGCHLD
当子进程
退出,暂停,从暂停恢复运行
,在子进程中会产生一个SIFGCHLD
信号,并将其发送给父进程
。父进程可以捕捉子进程发送过来的信号来回收子进程的资源
。
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <sys/wait.h> | |
#include <signal.h> | |
// 回收子进程处理函数 | |
void recycle(int num) | |
{ | |
printf("捕捉到的信号是: %d\n", num); | |
// 子进程的资源回收,非阻塞 | |
// SIGCHLD 信号 17 号信号,1-31 号信号不支持排队 17 子进程中执行完毕 | |
// 如果这些信号同时产生多个,最终处理的时候只处理一次 | |
// 假设多个子进程同时退出,父进程同时收到了多个 sigchld 信号 | |
// 父进程只会处理一次这个信号,因此当前函数被调用了一次,waitpid 被调用一次 | |
// 相当于只回收了一个子进程,但是是同时死了多个子进程,因此就出现了僵尸进程 | |
// 解决方案:循环回收即可 | |
while(1) | |
{ | |
// 如果是阻塞回收,就回不到另外一个处理逻辑上去了 | |
pid_t pid = waitpid(-1, NULL, WNOHANG); | |
if(pid > 0) | |
{ | |
printf("child died, pid = %d\n", pid); | |
} | |
else if(pid == 0) | |
{ | |
// 没有死亡的子进程,直接退出当前循环 | |
break; | |
} | |
else if(pid == -1) | |
{ | |
printf("所有子进程都回收完毕了, 拜拜...\n"); | |
break; | |
} | |
} | |
} | |
int main() | |
{ | |
// 设置 sigchld 信号阻塞 | |
sigset_t myset; | |
sigemptyset(&myset); // 将集合中的所有标志位设置为 0 | |
// 将 myset 集合中所有标志位设置为 0 | |
sigaddset(&myset, SIGCHLD); | |
// SIG_BLOCK 将参数 set 集合中的数据追加到阻塞信号集中 | |
sigprocmask(SIG_BLOCK, &myset, NULL); | |
// 循环创建多个子进程 - 20 | |
pid_t pid; | |
for(int i=0; i<20; ++i) | |
{ | |
// 循环创建 20 个进程 | |
pid = fork(); | |
if(pid == 0) // 子进程退出,父进程继续创建进程 | |
{ | |
break; | |
} | |
} | |
if(pid == 0) | |
{ | |
printf("我是子进程, pid = %d\n", getpid()); | |
} | |
else if(pid > 0) | |
{ | |
printf("我是父进程, pid = %d\n", getpid()); | |
// 注册信号捕捉,捕捉 sigchld | |
struct sigaction act; | |
act.sa_flags =0; | |
act.sa_handler = recycle; | |
sigemptyset(&act.sa_mask); | |
// 注册信号捕捉,委托内核处理将来产生的信号 | |
// 当信号产生之后,当前进程优先处理信号,之前的处理动作会暂停 | |
// 信号处理完毕之后,回到原来的暂停的位置继续运行 | |
sigaction(SIGCHLD, &act, NULL); | |
// 解除 sigcld 信号的阻塞 | |
// 信号被阻塞之后,就捕捉不到了,解除阻塞之后才能捕捉到这个信号 | |
sigprocmask(SIG_UNBLOCK, &myset, NULL); | |
// 父进程执行其他业务逻辑就可以了 | |
// 默认父进程执行这个 while 循环,但是信号产生了,这个执行逻辑或强迫暂停 | |
// 父进程去处理信号的处理函数 | |
while(1) | |
{ | |
sleep(100); | |
} | |
} | |
return 0; | |
} |