03-系统编程(信号)

系统编程(信号)

一、进程之间的通信 – 信号

1.什么是信号

信号是一种异步通信机制,一般情况下,进程什么时候会收到信号、收到什么信号是无法事先预料的
(就像你家的门铃,你不知道它什么时候会响,但是门铃响的时候我们可以下楼开门(处理))

2.在linux下,有哪些信号

在这里插入图片描述
例如:
19) SIGSTOP //signal 信号
信号值)SIG+信号名字

其实信号的名字与信号值是等价的,它们是宏定义来的,被定义在一个头文件:
/usr/include/asm-generic/signal.h

#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4

3.在linux下,这些信号究竟是由谁来发出?

(1)由系统来发出。
14) SIGALRM -> 当在程序中调用alarm()时,如果到点了,就会自动发出这个信号。
17) SIGCHLD -> 当子进程退出时,自动发出这个信号给父进程。SIGCHLD = signal child

(2)信号也可以由用户来发送。
如果是由用户来发送,则需要学习 kill / killall 这两个命令。

4.用户如何发送信号给进程?

方法一:

(1)首先查看目标进程的PID号。 -> ps -ef(或者使用getpid打印自己的进程id号)
PID号
gec 4630 4310 0 00:30 pts/4 00:00:00 ./a.out

(2)通过kill命令发送9号信号给该进程,杀死进程。(kill -9 杀死进程这个指令很常用)
kill -9 4630
kill -SIGKILL 4630 或者 kill -KILL 4630
说明:kill是给pid值发送信号

方法二:

直接通过killall命令给进程名字发送信号
killall -9 a.out
killall -SIGKILL a.out 或者 killall -KILL a.out -> 只要名字为a.out,都会收到这个信号。
说明:killall是给进程名发送信号

小练习:写一个进程fork之后,父进程一直执行,子进程也一直执行,分别用kill和killall干掉他们

代码参考:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main(void)
{
    pid_t id;
    id = fork();
    if (id == -1)
    {
        perror("open fork fail");
        return -1;
    }
    else if (id > 0)
    {
        while (1)
        {
            printf("[%d]父进程\n",getpid());
            sleep(1);
        }
        wait(NULL);
    }
    else if (id == 0)
    {
        while (1)
        {
            printf("[%d]子进程\n",getpid());
            sleep(1);
        }
        exit(0);
    }
    
    return 0;
}

二、关于信号的函数接口

1.如何发送信号给另外一个进程? -> kill()

#include <sys/types.h>
#include <signal.h> -> linux信号的专属头文件
int kill(pid_t pid, int sig);
函数作用:
向指定进程或者进程组,发送一个指定的信号
参数:
pid:进程号
sig:信号值

举例:

#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>

int main(int argc, char*argv[]) // ./a.out 3445
{
//获取你要发送的信号的进程的ID号
int id = atoi(argv[1]);
//发送信号
kill(id,SIGINT);

return 0;

}

2.如何捕捉信号? -> signal()

#include <signal.h>
typedef void (*sighandler_t)(int); //函数指针(函数句柄)
void(*sighandler_t)(int);//定义一个函数指针
sighandler_t signal(int signum, sighandler_t handler);
函数作用:
捕捉一个指定的信号,即预先为某信号的到来做好准备
参数:
signum 需要捕捉的信号
handler
SIG_IGN 忽略该信号 英/ɪɡˈnɔː®/ ignore
SIG_DFL 执行该信号的默认动作 英/dɪˈfɔːlt/ default
void (*p)(int) 执行由 p 指向的信号响应函数

返回值:
成功返回 最近一次调用该函数时第二个参数的值
失败返回 SIG_ERR (#define SIG_ERR -1)
注意:

1.所谓的捕捉信号就是获取当这个信号来之后,去执行信号响应函数,原本的信号默认动作就不会执行了。
2.当调用signal函数之后 ,程序不会阻塞,而是往下面代码执行,但是这个捕捉设置是全局有效
3.SIGKILL 、SIGSTOP 不能被捕捉,只能执行默认的动作
注意:
typedef void (*sighandler_t)(int); //给自定义的函数指针类型取了一个别名叫做sighandler_t
void (*sighandler_t)(int); //自定义了一个函数指针类型,这个指针指向的是一个函数,函数的参数时一个int,函数的返回值是void

举例:

#include<stdio.h>
#include <sys/types.h>
#include <signal.h>

//信号响应函数 ,也就是说signal捕捉到指定的信号之后,去执行这个函数
void signalHandle(int arg)
{
    printf("arg:%d\n",arg);
    printf("signalHandle 听说你想杀死我\n");
}

int main(int argc, char*argv[]) // ./a.out 3445
{
    //捕捉信号SIGINT,去执行信号响应函数
    signal(SIGINT,signalHandle); 

    while(1);

    return 0;
}


运行结果:
在这里插入图片描述

3.挂起进程,直到收到一个信号为止->pause()

#include <unistd.h>
int pause(void);
参数:无
返回值:
收到非致命信号或者已经被捕捉的信号 -1
收到致命信号导致进程异常退出 不返回

注意:pause( )是在响应函数返回之后,随后再返回的。

举例:

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

int main(int argc, char*argv[]) // ./a.out 3445
{
    printf("main start\n");

    //修改SIGUSR2信号的动作属性,将它变成非致命信号
    signal(SIGUSR2,signalHandle);

    //将当前进程挂起,直到收到一个信号,程序才会往下面执行
    pause();//会阻塞
        
    printf("end\n");
    return 0;
}

说明:
10) SIGUSR1与12) SIGUSR2 通常被开发者常用自定义
pause()可以充当while(1)的作用。

拓展:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

// 在C语言中函数名就是指针
void handler(int arg)
{
    printf("收到信号下午开始上课:%d\n",arg);
}

void handler2(int arg)
{
    printf("收到信号下午开始发信号:%d\n",arg);
}

int main()
{
    printf("[%d] main process\n",getpid());

    //响应信号的动作---handler
    signal(SIGUSR1,handler); //不会阻塞
    signal(SIGUSR2,handler2);


    //进程挂起,此处会阻塞,直到收到信号继续运行
    pause(); //阻塞  10000的写法

    //for(;;)它的效率要高于while(1)
    //原因是因为for(;;)反汇编后的汇编代码要比while(1)小
    // while(1); //5000 的写法
    // for(;;);    //8000

    return 0;
}

4.自己给自己发送信号-> raise()

#include <signal.h>
int raise(int sig);
参数:
sig: 发送的信号。
返回值:
成功:0
失败:非0

举例:

#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>

//信号响应函数 ,也就是说signal捕捉到指定的信号之后,去执行这个函数
void signalHandle1(int arg) //arg 信号值
{
    printf("arg:%d\n",arg);
    printf("signalHandle1 听说你想杀死我\n");
}

int main(int argc, char*argv[]) // ./a.out 3445
{
    printf("main start\n");
    //捕捉信号SIGUSR1,去执行信号响应函数
    signal(SIGUSR1,signalHandle1); 
    
    while(1)
    {
        sleep(1);
        //自己给自己发送信号SIGUSR1
        raise(SIGUSR1);
    }

    return 0;
}

运行结果:
在这里插入图片描述
小练习:使用raise实现如下效果:一直打印"我在打游戏",每隔10秒闹钟一次打印,“放下游戏出来搞学习”。

代码参考:

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

//arg是指你收到的信号的值
void handler(int arg)
{
    printf("放下游戏出来搞学习:%d\n",arg);
}

int main(void)
{
    int i = 0;
    
    //非阻塞一直生效
    signal(SIGUSR1, handler);
    while (1)
    {   
        //10秒之后给自己发一个信号SIGUSR1
        if(i == 10)
        {
            i = 0;
            raise(SIGUSR1);
        }

        printf("我在打游戏.....%d\n",i);

        i++;
        sleep(1);
    }

    return 0;
}

运行结果:
在这里插入图片描述
小练习:验证一下子进程退出的时候,是否给父进程发出了SIGCHLD信号

代码参考:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

//子进程退出会给父进程发送SIGCHLD信号
void handle(int arg)
{
    printf("收到子进程的退出信号:%d\n",arg);
    // wait(NULL);//wait函数可以放到这个地方来接受子进程资源
}

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
    }
    else if (id > 0) // 父进程
    {
        //修改SIGCHLD信号的响应动作
        //此处只适用于父进程
        signal(SIGCHLD,handle);

        wait(NULL);
    }
    else if (id == 0) // 子进程
    {

        exit(0);
    }

    return 0;
}

运行结果:
在这里插入图片描述

三、信号的处理(除SIGKILL,SIGSTOP)

1.忽略(将信号丢弃)
signal(signum,SIG_IGN); //signal ignore
2.缺省(默认动作)
signal(signum,SIG_DFL); //signal default
3.捕捉(去执行指定的函数)
signal(signum,function);
以三个动作都和signal有关,取决于第二个参数
4.阻塞(信号挂起)
设置阻塞之后,来了阻塞的指定信号,并不是将信号丢弃,而是将信号挂起,等到解除阻塞之后才去响应这个信号
这个动作和信号有关
注意:
1、不能够单独某一个信号为阻塞
2、先将信号添加到

注意:
9) SIGKILL和 19) SIGSTOP 这两个信号不能被忽略,阻塞、捕捉。必须是执行默认动作

四、linux系统信号集

1.什么是信号集?

信号集是一个集合,而每一个成员都是一个信号,通过将信号加入到信号集中,再设置阻塞状态给信号集,
那么整个信号集里面所有的信号都会变成阻塞的状态。

2.信号阻塞与信号忽略有什么区别?

信号响应: 收到信号之后,会响应信号的动作。 signal(signum,function);
信号忽略: 收到信号之后,直接丢弃这个信号。signal(signum,SIG_IGN); //signal ignore
信号阻塞: 进程在阻塞某一个信号前提下,收到了这个信号,不会马上响应,而是要等到解除阻塞之后,才会响应这个信号。
(这个信号没有被响应时,不会丢弃,而是放在一个挂起队列中)

五、信号集处理函数

1.信号集如何定义?
信号集其实就是一个变量,数据类型是: sigset_t。
定义信号集: sigset_t set
#include <signal.h>
int sigemptyset(sigset_t *set); 清空信号集
int sigfillset(sigset_t *set); 将linux下所有的信号都加入到信号集中(很少见)
int sigaddset(sigset_t *set, int signum); 在指定的信号集set中,添加一个指定的信号signum
int sigdelset(sigset_t *set, int signum);在指定的信号集set中,删除一个指定的信号signum
int sigdelset(sigset_t *set, int signum);在指定的信号集set中,删除一个指定的信号signum

int sigismember(const sigset_t *set, int signum); 测试某一个信号是不是在集合中
参数:
set:需要判断的信号集的地址
signum: 需要测试的信号
返回值:
成功:0
失败:-1
sigismember 函数在集合中返回1,不在集合中返回0,失败返回 -1

在这里插入图片描述
举例: 写一个程序,先清空信号集,再把SIGUSR1加入到集合中,判断信号是不是在集合中。

int main(int argc, char*argv[]) // ./a.out 3445
{
    //1)先定义一个信号集变量 
    sigset_t  set;
    //2) 初始化(清空)
    sigemptyset(&set); //清空信号集
    //3)将信号 添加到集合中 SIGUSR1  SIGUSR2
    sigaddset(&set,SIGUSR1); //在指定的信号集set中,添加一个指定的信号signum到集合中
    sigaddset(&set,SIGUSR2); 
    
    //4) 判断 SIGUSR2信号是否在集合中,如果在打印yes  ,否则 打印 no
    // sigismember 信号在集合中 返回1  否则 返回 0
    if(sigismember(&set, SIGUSR2))
        printf("yes\n");
    else 
        printf("no\n");
    
    return 0;
}

六、如何设置信号集为阻塞状态? -> sigprocmask()

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数:
how:
SIG_BLOCK -> 设置为阻塞的属性 block阻塞
SIG_UNBLOCK -> 解除阻塞
set: 你要设置哪个信号集,将这个信号集的地址传递过来
oldset:保留之前状态的指针,如果不关心,则填NULL。
返回值:
成功:0
失败:-1

举例:sigset set;
信号 -> set
sigprocmask(SIG_BLOCK,&set,NULL); -> 设置为阻塞

sigprocmask(SIG_UNBLOCK,&set,NULL); -> 解除阻塞

#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

#include <string.h>
#include <sys/wait.h>
#include <signal.h>

void signalHandle(int signum)
{
    printf("捕捉到%d信号,去执行别的事情.....\n",signum);
    int cnt = 5;
    while(cnt--)
    {
        sleep(1);
        printf("我正在清理垃圾...%d\n",cnt);
    }
}

//SIGINT
int main(int argc,char**argv)  // ./a.out  46725
{
    //将SIGUSR2信号设置一个信号响应函数
    signal(SIGUSR2,signalHandle);
    signal(60,signalHandle);
    signal(SIGINT,handle3); //ctrl+c相当于SIGINT,此时被修改了,所以ctrl+c无法终止进

    //1、先定义一个信号集合变量  ---数组 
    sigset_t set;
    //2、清空信号集合变量   -----数组清空
    sigemptyset(&set);
    //3、将你想要设置的信号  一个个地加入到集合变量中   --数组中每个元素挨个进行赋值 
    sigaddset(&set,SIGUSR1);
    sigaddset(&set,SIGUSR2);
    sigaddset(&set,60);

    //将上面的信号集合set中的所有信号  设置为阻塞状态
    sigprocmask(SIG_BLOCK, &set,NULL);

    int cnt = 30;
    while(cnt--){
        sleep(1);
        printf("[%d]主进程正在执行很重要任务%d.....\n",getpid(),cnt);
    }

    //等上面很重要的事情 完成了,再解除阻塞 ,响应信号
    sigprocmask(SIG_UNBLOCK, &set,NULL);

    return 0;
}

小练习: 创建一个子进程,父进程将SIGUSR1加入到信号集中,判断信号在不在集合中,再设置该信号为阻塞状态,
该状态持续10s(那么在10S内,对SIGUSR1都是阻塞的),10s后,解除阻塞,看看会不会响应信号?
子进程在10S内发送一个SIGUSR1给父进程。
1)主要看,信号发过去之后,如果是马上响应----信号响应
2)如果是10s后响应—信号阻塞—》对
3)永远都不响应----》信号忽略

代码参考:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

void handle(int arg)
{
    printf("今天又是元气满满的一天:%d\n", arg);
}

int main()
{
    /*
        父进程空间
    */
    pid_t id = fork();
    if (id == -1)
    {
        perror("fork fail");
        return -1;
    }
    else if (id > 0) // 父进程
    {
        //信号的响应函数
        signal(SIGUSR1, handle);

        // 1)先定义一个信号集变量(不要定义成指针变量*set)
        sigset_t set;
        // 2) 初始化(清空)(通过函数sigemptyset给set赋值 传地址)
        sigemptyset(&set); // 清空信号集
        // 3)将信号 添加到集合中 SIGUSR1  SIGUSR2
        sigaddset(&set, SIGUSR1); // 在指定的信号集set中,添加一个指定的信号signum到集合中
        // 4) 判断信号SIGUSR1是否在集合中
        if (sigismember(&set, SIGUSR1))
            // if(sigismember(&set, SIGINT))
            printf("yes\n");
        else
            printf("no\n");

        // 设置信号集为阻塞 --- 次持续10秒
        sigprocmask(SIG_BLOCK, &set, NULL); // -> 设置为阻塞

        int cnt = 10;
        while(cnt--)
        {
            printf("新的一天,头脑不能阻塞.....%d\n",cnt);
            sleep(1);
        }
        sigprocmask(SIG_UNBLOCK, &set, NULL); // -> 设置为非阻塞

    }
    else if (id == 0) // 子进程
    {
        sleep(5);

        printf("子进程给父进程发送SIGUSR1信号\n");

        //子进程5秒之后给父进程发送SIGUSR1信号
        kill(getppid(),SIGUSR1); 
    }

    return 0;
}

运行结果:
在这里插入图片描述
小练习:验证阻塞属性会被子进程继承。(验证的时候可以其它进程给子进程发送信号)

代码参考:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

void handle(int arg)
{
    printf("今天又是元气满满的一天:%d\n", arg);
}

int main()
{
    /*
        父进程空间:这一段空间父进程会拷贝一份给父进程
    */

    signal(SIGUSR1, handle);

    // 1)先定义一个信号集变量(不要定义成指针变量*set)
    sigset_t set;
    // 2) 初始化(清空)(通过函数sigemptyset给set赋值 传地址)
    sigemptyset(&set); // 清空信号集
    // 3)将信号 添加到集合中 SIGUSR1  SIGUSR2
    sigaddset(&set, SIGUSR1); // 在指定的信号集set中,添加一个指定的信号signum到集合中

    // 设置信号集为阻塞 --- 次持续10秒
    sigprocmask(SIG_BLOCK, &set, NULL); // -> 设置为阻塞


    pid_t id = fork();
    if (id == -1)
    {
        perror("fork fail");
        return -1;
    }
    else if (id > 0) // 父进程
    {
        /*
            父进程10秒之后变成了非阻塞
        */

        int cnt = 10;
        while (cnt--)
        {
            printf("[%d]我是你的好大爹...%d\n",getpid(),cnt);
            sleep(1);
        }
        sigprocmask(SIG_UNBLOCK, &set, NULL); // -> 设置为非阻塞

        while(1);

    }
    else if (id == 0) // 子进程
    {
        /*
            子进程一直都是阻塞
        */
        int cnt=0;
        while(1)
        {
            printf("[%d]我是你的好大儿...%d\n",getpid(),cnt++);
            sleep(1);
        }
    }

    return 0;
}

拓展

alarm(5)//定时5秒自己给自己发送SIGALRM
练习告诉自己10秒之后,把手机扔掉,搞学习。
代码参考:

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

void signalHandle( int sigNum )
{
    printf("把手机扔掉, 搞学习\n");
}

int main(int argc, char const *argv[])
{
    signal( SIGALRM, signalHandle );

    alarm(5); //5秒之后给自己发送SIGALRM信号

    pause(); //收到SIGALRM信号之后,进程不再挂起,继续执行
    // while(1);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值