信号
信号本质是一个整数,不同的信号对应不同的值,由于信号的结构简单,不能携带大量信息量,在系统中的优先级非常高
信号编号
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,调用失败返回-1sigset_ts初始化
在程序中读写
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;
}