信号发送与信号处理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/daaikuaichuan/article/details/82873442

一、信号发送

1、kill函数

  进程可以通过kill函数向包括它本身在内的其他进程发送一个信号,如果程序没有发送这个信号的权限,对kill函数的调用就将失败,而失败的常见原因是目标进程由另一个用户所拥有。

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
  • 功能: 把信号sig发送给进程号为pid的进程。
  • 参数:
    • pid:取值有 4 种情况:
      • pid > 0:将信号传送给进程 ID 为pid的进程;
      • pid = 0:将信号传送给当前进程所在进程组中的所有进程;
      • pid = -1:将信号传送给系统内所有的进程;
      • pid < -1:将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
    • sig:信号的编号,这里可以填数字编号,也可以填信号的宏定义。
  • 返回值: 成功:0,失败:-1。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
 
void fa(int signo)
{
    printf("捕获到了信号%d\n", signo);
}
 
int main(void)
{
    //使用fork函数创建子进程
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork"),exit(1);
    }
    if(0 == pid) //子进程
    {
        printf("pid = %d\n", getpid());
        //设置对信号50进行自定义处理
        signal(50, fa); // 捕捉信号
        while(1);
    }
    sleep(1);
    //判断子进程是否存在
    if(0 == kill(pid,0)) 
    {
        printf("父进程发送信号50\n");
        kill(pid,50); //使用kill函数发送信号
    }
    return 0;
}

2、raise函数

#include <signal.h>
int raise(int sig);
  • 功能: 向调用进程自己发送信号。
  • 参数:
    • sig:信号的编号。
  • 返回值: 成功:0,失败:-1。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
 
int main (void)
{
    printf("按回车键,终止进程\n");
    getchar();
    raise(SIGTERM); // 进程自杀
    while(1)
        pause();
    return 0;
}

二、信号等待(进程休眠)

1、alarm函数

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
  • 功能: 使用 alarm 函数可以设置一个定时器(闹钟时间),在将来的某个时刻该定时器会超时。当定时器超时时,产生 SIGALRM 信号。如果忽略或不捕捉此信号,则其默认动作是终止调用该 alarm 函数的进程。
  • 参数:
    • seconds :产生信号 SIGALRM 需要经过的时钟秒数。当这个时刻到达时,信号由内核产生。
  • 返回值: 返回 0 或先前所设闹钟的剩余秒数。
#include <stdio.h>
#include <unistd.h>

int main()
{
    int cnt;
    alarm(1); // 设置定时器,在将来的某个时刻定时器超时,程序结束
    for (cnt = 0; 1; ++cnt)
        printf("%d ", cnt); // 计算系统在一秒内可以计算到多大的数
    return 0;
}

2、pause函数

#include <unistd.h>
int pause(void);
  • 功能: pause 函数使调用进程挂起直至捕捉到一个信号(无限睡眠)。
  • 参数:
  • 返回值: 成功:阻塞,失败返回 -1。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
 
void sigint (int signo) // 信号处理函数
{
    printf ("\n中断符号被发送\n");
}

int main(void)
{
    if (signal(SIGINT, sigint) == SIG_ERR)
        perror("signal"), exit(1);
    printf("按 ctrl+c 继续\n");
    // pause要么不返回,要么返回-1,且errno设置为enter
    if (pause() != -1 && errno != EINTR) 
        perror("pause"), exit(1);
    printf("进程继续\n");
    return 0;
}
输出结果:
按 ctrl+c 继续
^C
中断符号被发送
进程继续

3、sleep函数

#include <unistd.h>
unsigned int sleep(unsigned int seconds);
  • 功能: 有限睡眠。
  • 参数:
    • seconds :指定要睡眠的时间。
  • 返回值: 返回 0 或剩余秒数。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
 
void sigint (int signo)
{
	printf ("\n中断符号被发送\n");
}
 
int main (void)
{
	if (signal (SIGINT, sigint) == SIG_ERR)
		perror ("signal"), exit (1);
	printf ("按 ctrl+c 继续\n");
	int res = sleep (60);
	if (res)
		printf ("进程被提前%d秒叫醒\n", res);
 
	printf ("进程继续\n");
	return 0;
}
输出结果:
按 ctrl+c 继续
^C
中断符号被发送
进程被提前57秒叫醒
进程继续

三、信号处理

1、signal函数

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
  • 功能: 注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞。
  • 参数:
    • sig:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l (“l” 为字母)进行相应查看。
    • func:一个类型为void (*)(int)的函数指针,该函数返回一个与func相同类型的指针,指向先前指定信号处理函数的函数指针。准备捕获的信号的参数由sig给出,接收到的指定信号后要调用的函数由参数func给出。注意信号处理函数的原型必须为void func(int)形式
  • 返回值: 成功:返回以前的信号配置;出错,返回SIG_ERR。

【Note】:因为子进程在开始时复制了父进程的内存映像,所以子进程继承父进程的信号处理方式。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
 
// 信号处理函数
void signal_handler(int signo)
{
	if(signo == SIGINT){
		printf("recv SIGINT\n");
	}else if(signo == SIGQUIT){
		printf("recv SIGQUIT\n");
	}
}
 
int main(int argc, char *argv[])
{
	printf("wait for SIGINT OR SIGQUIT\n");
	
	/* SIGINT: Ctrl+c ; SIGQUIT: Ctrl+\ */
	// 信号注册函数
	signal(SIGINT, signal_handler);
	signal(SIGQUIT, signal_handler);
	
	// 等待信号
	pause();
	pause();
	
	return 0;
}

2、sigaction函数

sigaction结构体:

struct sigaction
{
 	//信号处理函数
    void (*sa_handler)(int);
    // 调用信号捕捉函数前,该信号集被加到信号屏蔽字中,
    // 从而在调用信号捕捉函数时,能阻塞某些信号。
    sigset_t sa_mask;  
    // ;信号处理修改器。
    int sa_flags;
    // 替代的信号处理程序,一次只能使用sa_handler和sa_sigaction中的一个。
    void (*sa_sigaction)(int, siginfo_t *, void *);
};

在这里插入图片描述
【Note】:同一信号多次发生,并不将它们加入队列;如:某种信号阻塞时发生了5次,解除阻塞后,信号处理函数只调用一次。

  sigaction函数的原型为:

#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
  • 功能: 处理信号。
  • 参数:
    • sig:信号的编号;
    • act:用于设置指定信号的动作;
    • oact:一般为NULL,如果不是空指针的话,就用它来保存原先对该信号的动作的位置。
  • 返回值: 成功:0,失败:-1。
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
 
void oldsigint(int signum) // 信号处理函数
{
    printf ("\n%d进程:收到%d信号\n", getpid (), signum);
}

int main()
{
    struct sigaction sigact = {}; // 定义sigaction结构体
    // 指定信号处理函数
    sigact.sa_handler = oldsigint;
    // 用于设置在信号处理函数的执行期间,需要屏蔽的信号
    sigaddset(&sigact.sa_mask, SIGINT);
    // 设置信号处理的标志
    // SA_NODEFER表示解除对相同信号的屏蔽
    // SA_RESETHAND表示处理信号后恢复默认处理方式
    sigact.sa_flags = SA_NODEFER | SA_RESETHAND;
    //sigact.sa_flags = 0; // 不适用处理标志
    // 注册信号和结构体
    if (sigaction(SIGINT, &sigact, NULL) == -1)
    {
        perror("sigaction");
        exit(1);
    }
    while (1)
    {
        printf("************\n");
        sleep(1);
    }
    return 0;
}

四、信号集

  我们已经知道,我们可以通过信号来终止进程,也可以通过信号来在进程间进行通信,程序也可以通过指定信号的关联处理函数来改变信号的默认处理方式,也可以屏蔽某些信号,使其不能传递给进程。那么我们应该如何设定我们需要处理的信号,我们不需要处理哪些信号等问题呢?信号集函数就是帮助我们解决这些问题的。

1、信号的递送、阻塞和未决

  实际执行信号的处理动作称为信号递送(Delivery),信号从产生到递送之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号,SIGKILL 和 SIGSTOP 不能被阻塞。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递送的动作。
  每个进程都有一个信号掩码,它实际上是一个信号集,位于该信号集中的信号一旦产生,并不会被递送给相应的进程,而是会被阻塞在未决状态。在信号处理函数执行期间,这个正在被处理的信号总是处于信号掩码中,如果又有该信号产生,则会被阻塞,直到上一个针对该信号的处理过程结束以后才会被递送。

在这里插入图片描述
在这里插入图片描述

2、sigprocmask函数

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 功能: 设置调用进程的信号掩码。
  • 参数:
    • how:为修改信号掩码的方式,可取以下值:
      • SIG_BLOCK:将 sigset 中的信号加入当前信号掩码;
      • SIG_UNBLOCK:从当前信号掩码中删除 sigset 中的信号;
      • SIG_SETMASK :把 sigset 设置成当前信号掩码。
    • set:信号集,取 NULL 则忽略此参数。
    • oldset:输出原信号掩码,取 NULL 则忽略此参数。
  • 返回值: 成功:0,失败:-1。

3、sigpending函数

#include <signal.h>
int sigpending(sigset_t *set);
  • 功能: 获取调用进程的未决信号集。
  • 参数:
    • set:信号集,取 NULL 则忽略此参数。
  • 返回值: 成功:0,失败:-1。

4、信号集函数的应用

#include <signal.h>
// 清空信号集,即将信号集的全部信号位清 0。
int sigemptyset(sigset_t *set); 
// 填满信号集,即将信号集的全部信号位置 1。
int sigfillset(sigset_t *set);
// 加入信号,即将信号集中与指定信号编号对应的信号位置 1。
int sigaddset(sigset_t *set, int signum);
// 删除信号,即将信号集中与指定信号编号对应的信号位清 0。
int sigdelset(sigset_t *set, int signum);
// 以上4 个函数返回值:若成功,返回 0,;若出错,返回 -1
// 判断信号集中是否有某信号,即检查信号集中与指定信号编号
// 对应的信号位是否为 1。
int sigismember(const sigset_t *set, int signum);
// 返回值:若真,返回 1;若假,返回 0

【Demo】:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static void sig_int(int)
{
    printf("caught SIGINT\n");
    if (signal(SIGINT, SIG_DFL) == SIG_ERR)
        perror("can't reset SIGINT");
}

int main()
{
    sigset_t newmask, oldmask, pendmask;
    if (signal(SIGINT, sig_int) == SIG_ERR)
        perror("can't catch SIGINT");
    sigemptyset(&newmask); // 清空信号集
    sigaddset(&newmask, SIGINT); // 加入信号(阻塞 SIGQUIT 信号)
     // 保存当前的信号屏蔽字
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        perror("SIG_BLOCK error");
    sleep(5); // 休眠五秒,在此期间,SIG_QUIT信号都会被阻塞
    if (sigpending(&pendmask) < 0) // 获取当前信号的未决信号集
        perror("sigpending error");
    if (sigismember(&pendmask, SIGINT)) // 判断信号集中有没有SIG_QUIT信号
        printf("\nSIGQUIT pending\n");
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) // 不再阻塞原信号
        perror("SIG_SETMASK error");
    printf("SIGINT unblocked\n");
    sleep(5);
    return 0;
}

五、abort函数与sigqueue函数

1、abort函数

#include <stdlib.h>
void abort(void);

功能: abort 函数的功能是使程序异常终止。如果 abort 函数导致进程终止,则所有打开的流都将关闭并刷新。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
 
void fa(int signo)
{
    printf("捕捉到了信号%d\n", signo);
}
int main (void)
{
    signal(SIGABRT, fa);
    printf("1111111111111\n");
    abort();
    printf("2222222222222\n");
    return 0;
}

2、sigqueue函数

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
  • 功能: 表示向指定进程发送指定的信号和附加数据。
  • 参数:
    • pid:接收信号进程的 PID;
    • sig:信号编号;
    • value:附加数据。
  • 返回值: 成功:0,失败:-1。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
 
void fa(int signo, siginfo_t* info, void* p)
{
    printf("进程%d发送来了信号%d,附加数据是:%d\n", 
    		info->si_pid, signo, info->si_value);
}
 
int main(void)
{
    //定义结构体变量进行初始化
    struct sigaction action = {};
    //给第二个函数指针进行初始化
    action.sa_sigaction = fa;
    //给处理标志进行赋值
    //表采用结构中第二个函数指针处理
    action.sa_flags = SA_SIGINFO;
    //使用sigaction对信号40自定义处理
    sigaction(40, &action, NULL);
    //创建子进程给父进程发信号和数据
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork"),exit(-1);
    }
    if(0 == pid) //子进程
    {
        int i = 0;
        for(i = 0; i < 100; i++)
        {
            //定义联合进行初始化
            union sigval v;
            v.sival_int = i;
            //发送信号和附加数据
            sigqueue(getppid(), 40, v);
        }
        sleep(1);
        exit(1); //终止子进程
    }
    //父进程等待处理信号和附加数据
    while(1);
    return 0;
}

参考:https://www.cnblogs.com/wuchanming/p/4381574.html
https://blog.csdn.net/column/details/14909.html?&page=1
https://blog.csdn.net/lianghe_work/article/details/46804469
https://blog.csdn.net/ZX714311728/article/details/53056927

猜你喜欢

转载自blog.csdn.net/daaikuaichuan/article/details/82873442