1 睡眠、暂停、闹钟
1.1 sleep() 睡眠(阻塞)
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
功能:有限睡眠
seconds:以秒为单位的睡眠时限
返回值:0或剩余秒数
该函数是调用函数睡眠seconds秒,除非有信号终止了调用进程或被该进程捕获。
如果有信号被调用进程捕获,在信号处理函数返回以后,阻塞的sleep()函数才会返回,且返回值为剩余的秒数。否则函数将返回0,表示睡眠充足。
sleep()函数会被信号打断。信号捕获后,信号处理函数执行完,sleep()才关闭,进程继续。
1.2 usleep() 睡眠(阻塞)
#include <unistd.h>
int usleep(useconds_t usec);
功能:更精确的有限睡眠
usec:以微秒(10的负6次方秒)为单位的睡眠时限
返回值:成0败-1
如果有信号被调用进程捕获,在信号处理函数返回后,阻塞的usleep()函数才会返回,且返回-1,同时置errno为EINTR,表示阻塞的系统调用被信号中断。
1.3 pause() 无限睡眠(阻塞)
#include <unistd.h>
int pause(void);
功能:暂停(无限睡眠)
返回值:成功阻塞,失败返回-1
pause()函数要么阻塞不返回,要么返回-1,永远不会返回0。
pause()函数是调用进(线)程进入无时限的睡眠状态,知道有信号终止了该进程或信号被该进程捕获。(看电影时按下pause,进入无限睡眠,收到信号后,关闭pause,继续看电影)
如果有信号被调用进程捕获,在信号处理函数返回以后,阻塞的pause()函数才会返回,返回值-1,同时置errno为EINTR,表示阻塞的系统调用被信号打断。
pause()函数会被信号打断。信号捕获后,信号处理函数执行完,pause()才关闭,进程继续。
需要程序暂停时,调用pause(),需要继续时,给个信号即可。
//pause()函数演示
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
//信号处理函数
void sigfun(int signum){
printf("%d号信号处理开始\n",signum);
sleep(3);
printf("%d号信号处理结束\n",signum);
}
int main(void){
//捕获2号信号
if(signal(SIGINT,sigfun) == SIG_ERR){
perror("signal");
return -1;
}
printf("%d进程:我要睡觉了\n",getpid());
int ret = pause();//进入暂停,若捕获到2号信号,则执行信号处理函数,完毕后,暂停才结束。
printf("%d进程;pause函数返回%d\n",getpid(),ret);
return 0;
}//编译执行,择机输入Ctrl + C
1.4 alarm() 闹钟(非阻塞)
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:设置闹钟
seconds:以秒为单位的闹钟时间。取0表示取消先前设过且未到期的闹钟。
返回值:0或先前所设闹钟的剩余秒数
alarm()函数使系统内核在该函数被调用以后seconds秒的时候,向调用进程发送SIGALRM (14)信号。
若在调用该函数前已设置过另一个该函数且尚未到期,则函数会重设闹钟,并返回先前所设闹钟的剩余秒数,否则返回0。
2 信号集
多个信号组成的信号集合。
系统内核用sigset_t结构体类型表示信号集。
-在<signal.h>中又被定义为typedef __sigset_t sigset_t;
-在<sigset.h>中有如下类型定义
#define _SIGSET_NWORDS (1024 / (8* sizeof (unsigned long int)))
typedef struct {
unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;
sigset_t类型是一个结构体,该结构体中只有1个成员,是1个包含32个元素的整数数组(针对32位系统而言)。 (那直接用数组不就行了?答:约定俗称吧。)
故可以把sigset_t类型看成一个由1024个二进制位组成的大整数。
对于64位系统,long是8字节,数组是16个元素,依然共1024位:
其中的每一位对应一个信号,当然,目前远没有那么多信号。
某位为1就表示信号集中有此信号,反之为0就是无此信号。
当需要同时操作多个信号时,常以sigset_t作为函数的参数或返回值的类型。
2.1 sigfillset()
#include <signal.h>
int sigfillset (sigset_t* sigset);
功能:常用于初始化,填满信号集,即,将信号集的全部位,置1
sigset:信号集
返回值:成0败-1
2.2 sigemptyset()
#include <signal.h>
int sigemptyset (sigset_t* sigset);
功能:常用于初始化,清空信号集,即,将信号集的全部位,置0
sigset:信号集
返回值:成0败-1
2.3 sigaddset()
#include <signal.h>
int sigaddset (sigset_t* sigset, int signum);
功能:加入信号,即,将信号集中与指定信号编号对应的信号位,置1
sigset:信号集
signum:信号编号
返回值:成0败-1
2.4 sigdelset()
#include <signal.h>
int sigdelset (sigset_t* sigset, int signum);
功能:删除信号,即,将信号集中与指定信号编号对应的信号位,置0
sigset:信号集
signum:信号编号
返回值:成0败-1
2.5 sigismember()
#include <signal.h>
int sigismember (const sigset_t* sigset, int signum);
功能:判断信号集中是否有某信号,即检查信号集中与指定信号编号对应的信号位为1否
sigset:信号集
signum:信号编号
返回值:有则返回1没则返回0,失败返-1
//sigset.c 信号集
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
//打印一个字节八位内容
void printb(char byte){ // b
for(int i = 0;i < 8;i++){
printf("%d",byte & 1 << 7 - i ? 1 : 0);
}
printf(" ");//空格
}
// 一块存储区的所有比特位内容的输出
void printm(void* buf,size_t size){ // m
for(int i = 0;i < size;i++){
printb(((char*)buf)[size-1-i]); //字节倒着输出,与bit顺序一致
if((i + 1) % 8 == 0){
printf("\n");
}
}
}
int main(void){
//信号集
sigset_t s;//欠初始化
printf("填满信号集:\n");
sigfillset(&s);//初始化
printm(&s,sizeof(s));
printf("清空信号集:\n");
sigemptyset(&s);
printm(&s,sizeof(s));
printf("添加2号信号\n");
sigaddset(&s,SIGINT);
printm(&s,sizeof(s));
printf("添加3号信号\n");
sigaddset(&s,SIGQUIT);
printm(&s,sizeof(s));
printf("删除2号信号\n");
sigdelset(&s,SIGINT);
printm(&s,sizeof(s));
printf("信号集中%s2号信号\n",sigismember(&s,SIGINT) ? "有" : "无");
printf("信号集中%s3号信号\n",sigismember(&s,SIGQUIT) ? "有" : "无");
return 0;
}//编译执行,注意下低32、低33位
3 信号屏蔽
3.1 递送、未决、掩码
递送(delivery):当信号产生时,系统内核会在其所维护的进程表中,为特定的进程设置一个与该信号相对应的标志位,这个过程叫做递送。
未决(pending):信号从产生到完成递送之间存在一定的时间间隔,处于这段时间间隔中的信号称为未决。
掩码(signal mask):每个进程都有一个信号掩码,它实际是一个信号集,位于该信号集中的信号一旦产生,并不会被递送给响应进程,而是会被阻塞(block)在未决状态。
相同不可靠信号瞬时发送多个,只接收2个的原因:
捕获到某信号,在信号处理函数执行期间,这个正在被处理的信号总是会处于信号掩码中。如果又有该信号产生,则会被阻塞,直到上一个针对该信号的处理过程结束以后才会被递送。(相同不可靠信号瞬时来多个,只接受2个,其余丢弃,的出处在这。)
信号掩码的意义:
当信号正在执行类似更新数据库这样的敏感任务时,可能并不希望被某些信号中断。这时可以通过信号掩码暂时屏蔽而非忽略这些信号,使其一旦产生即被阻塞于未决状态(即屏蔽),待任务完成后,再回过头来处理这些信号。
3.2 sigprocmask()
#include <signal.h>
int sigprocmask (int how, const sigset_t* sigset, sigset_t* oldset);
功能:设置调用进程的信号掩码
how:对信号掩码进行何种修改,可取以下值
SIG_BLOCK 将sigset中的信号加入到当前信号掩码
SIG_UNBLOCK 将sigset中的信号从当前信号掩码中删除
SIG_SETMASK 用sigset取代当前信号掩码
sigset:信号集,取NULL则忽略此参数
oldset:输出原信号代码(方便后续复原),取NULL则忽略此参数
返回值:成0败-1
//sigprocmask.c 信号掩码
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d号信号\n",getpid(),signum);
}
//假装更新数据库
void updatedb(void){
for(int i = 0;i < 5;i++){
printf("正在更新第%d条数据\n",i+1);
sleep(1);
}
}
int main(void){
int signum = /*SIGINT*/ 50; //注意不可靠信号、可靠信号
//对2号信号进行捕获处理
if(signal(signum,sigfun) == SIG_ERR){
perror("signal");
return -1;
}
//对2号信号进行屏蔽处理, 屏蔽期间信号阻塞,无法捕获
printf("%d进程:对%d号信号进行屏蔽\n",getpid(),signum);
sigset_t sigset;
sigemptyset(&sigset);//清空
sigaddset(&sigset,signum);//添加2号信号
sigset_t oldset;//用来输出修改前的信号掩码
if(sigprocmask(SIG_SETMASK,&sigset,&oldset) == -1){
perror("sigprocmask");
return -1;
}
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码,向父进程发送信号
if(pid == 0){
for(int i = 0;i < 5;i++){
printf("%d进程:向父进程发送%d号信号\n",getpid(),signum);
if(kill(getppid(),signum) == -1){
perror("kill");
return -1;
}
}
return 0;
}
//父进程代码,更新数据库
updatedb();
//父进程解除2号信号屏蔽
printf("%d进程:解除对%d号信号的屏蔽\n",getpid(),signum);
if(sigprocmask(SIG_SETMASK,&oldset,NULL) == -1){
perror("sigprocmask");
return -1;
}
//父进程收尸
if(wait(NULL) == -1){
perror("wait");
return -1;
}
return 0;
}//编译执行
编译执行,结果如下:
解释如下:
对于不可靠信号(1~31),通过sigprocmask()函数设置信号掩码以后,每种被屏蔽信号中只有第一个会被阻塞,并在解除屏蔽后被递送,其余的则全部丢失。
对于可靠信号(34~64),通过sigprocmask()函数设置信号掩码以后,每种被屏蔽信号中的每个信号都会被阻塞,并按先后顺序排队,一旦解除屏蔽,这些信号会被一次递送。
3.3 sigpending()
#include <signal.h>
int sigpending (sigset_t* sigset);
功能:获取调用进程的未决信号集
sigset:输出未决信号集
返回值:成0败-1