Linux进程通信(管道、消息队列、信号)

Linux进程通信

概述

Linux系统给进程通信(IPC)提供了很多中方法,主要有以下这些:

  • 管道

  • 信号

  • 消息队列

  • 共享内存

  • 信号量

  • 套接字

这些方法需要根据不同场合和条件使用。

一、管道

使用管道来进行进程通信是最简单的一种方式,管道分为pipe无名管道和FIFO有名管道。

1.1 pipe无名管道

pipe函数的原型:

int pipe(int pipefd[2]);

其中pipefd[0]用于读管道,pipefd[1]用于写管道。

返回值:执行成功返回1,失败返回-1。

一般先使用pipe创建一个空管道,然后使用fork创建子进程,父子进程间通过pipe的读写端通道进行读写通信。更详细可以参考博客:https://blog.csdn.net/qq_42914528/article/details/82023408

在这里插入图片描述

pipe创建一个空管道之后,使用fork创建进程后,父子进程都有读写管道的文件描述符,pipefd[0]pipefd[1]在调用pipe函数创建管道之后都会被打开,并且父子两个进程都存在一套描述符,可以选择操作读端还是0写端。但是这里需要注意几个地方。

  1. 如果管道缓冲区为空,当有进程对该管道进行read读操作时,read是否阻塞等待数据取决于当前是否还有可以向管道写数据的文件描述符。即如果当前管道写端的文件描述符都关闭了的话,read函数就会直接返回0,否则如果还有可以向管道写数据的文件描述符,read就会阻塞直到缓冲区有数据可以读。
  2. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。
  3. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

下面使用父进程对管道进行写操作,使用fork创建的子进程进行读操作,实现父子进程间的通信。

源码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
//函数声明
void read_data(int pipes[]);
void write_data(int pipes[]);

int main(int argc, char **argv)
{
    int pipes[2], rc;
    pid_t pid;

    rc = pipe(pipes);//创建一个空管道,pipes[0]是用于读的fd,pipes[1]是用于写的fd
    if(rc == -1)
    {
        perror("\npipes Error");//输出函数调用产生的错误
        exit(1);
    }

    pid = fork();//创建进程
    switch (pid)
    {
        case -1://fork错误
            perror("\nfork Error:");
            exit(1);
            break;
        case 0://当前进程为子进程
            read_data(pipes);//使用子进程读
            break;
        default:
            write_data(pipes);//父进程写
            break;
    }
    return 0;
}

void read_data(int pipes[])
{
    int c, rc;

    close(pipes[1]);
    while((rc = read(pipes[0], &c, 1)) > 0)//读取pipes[0]到变量c中
    {
        putchar(c);//输出读到的字符
    }
    printf("close read_data!\n");
    exit(0);
}

void write_data(int pipes[])
{
    int c, rc;

    close(pipes[0]);//关闭当前进程的读端文件描述符
    while((c = getchar()) > 0)//从输入中获取变量c中
    {
        rc = write(pipes[1], &c, 1);
        if(rc < 0)//写入错误
        {
            perror("\nwrite Error");
            close(pipes[1]);//关闭文件
            exit(1);//退出
        }
    }
    close(pipes[1]);
    exit(0);
}

在主函数中,根据进程ID判断父子进程,使用父子进程对管道进行不同的读写操作。

执行结果:

在这里插入图片描述

为什么不使用sleep函数也可以确保read函数可以读到数据输出?

:前面说过当缓冲区中没有数据并且管道写端文件描述符没有全部关闭时,read操作会把阻塞等待数据,当我们父进程write写入数据时read就可以停止阻塞执行输出了。

验证:修改write_data函数,读写端文件描述符都关闭掉,子进程执行read前已经关闭了自己的写端文件描述符,所以当前管道的所有fd都已经关闭,read会直接返回0,打印printf(“close read_data!\n”);。

在这里插入图片描述

结果:子进程直接打印close read_data!,父进程输入数据fd[1]已经关闭无法写入,提示文件描述符损坏!

在这里插入图片描述

注意:使用pipe无名管道的IPC机制虽然非常简单,但是只能用于有亲缘关系的进程通信,如上面的父子进程通信。

1.2 FIFO命名管道

FIFO就是first in first out,先进先出的机制,先写入的数据会被先读出来。

使用FIFO有名管道可以实现无亲缘关系的通信。

相关函数:

int mkfifo(const char *pathname, mode_t mode);

mkfifo用于创建一个FIFO管道,让我们来看看man手册对它的描述:

在这里插入图片描述

返回值:

在这里插入图片描述

根据这些描述,参数pathname为要创建的fifo管道的路径和名称,mode就是管道的权限,与文件的权限相似。

返回值:失败返回-1,成功返回0。

特别要注意mkfifo返回值不是文件句柄,创建完fifo之后我们还需要打开fifo,操作使用文件操作的open函数,用法一致。但是要注意文件open的O_NONBLOCK标志位的影响:

  1. 不使用O_NONBLOCK标志位:只读打开管道会阻塞到有进程只写打开管道,同样只写方式打开要阻塞到其他进程只读方式打开这个管道。

  2. 使用O_NONBLOCK时,产生下列影响:
    对于读端:如果指定了O_NONBLOCK,则只读打开立即返回。

    对于写端:但是,如果没有进程已经为读打开一个FIFO,那么,只写打开将出错返回,其errno是 ENXIO:6号

writefifo.c:

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

#define BUF_SIZE PIPE_BUF

int main(int argc, char **argv)  
{   
    char writeBuf[] = "I Love Linux!";//要写入的数据
    int fifo_fd;
    if(argc < 2)
    {
        printf("usage: ./%s + fifo_name", argv[1]);
        exit(1);
    }

    if(mkfifo(argv[1], 0777) != -1)
    {   //创建fifo成功
        printf("mkfifo %s success!\n", argv[1]);
        if((fifo_fd = open(argv[1], O_WRONLY)) > 0)//打开fifo文件,获取句柄
        {
            printf("open fifo success, the fd is:%d\n", fifo_fd);
            int bytesWrite = write(fifo_fd, writeBuf, sizeof(writeBuf));//写入fifo管道
            if(bytesWrite > 0)
            {
                printf("write success,the buf is:\n");
                printf("%s\n", writeBuf);
                close(fifo_fd);
            }
            else
            {
                perror("write");
                exit(1);    
            }
        }
    }
    else
    {   
        printf("errno = %d\n", errno);
        perror("mkfifo failed");
        exit(1);
    }
    close(fifo_fd);
    return 0;
} 

readfifo.c:

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

#define BUF_SIZE 140

int main(int argc, char **argv)  
{   
    char dataBuf[BUF_SIZE] = {0};
    int fifo_fd;
    if(argc < 2)
    {
        printf("usage: %s + fifo_name \n", argv[0]);
        exit(1);
    }

    if(access(argv[1], F_OK) < 0)
    {
        printf("%s not exist!\n", argv[1]);
        exit(1);
    }

    if( (fifo_fd = open(argv[1], O_RDONLY)) > 0)
    {   //打开fifo成功
        printf("open fifo: %s success!\n", argv[1]);
        int bytesRead = read(fifo_fd, dataBuf, BUF_SIZE);
        if(bytesRead > 0)
        {
            printf("read success! the buf is: %s\n", dataBuf);
        }
        else
        {
            perror("read");
            printf("read fifo failed!\n");
        }
    }

    close(fifo_fd);
    return 0;
} 

在writefifo中我们使用main函数传入的参数创建一个fifo管道,然后向管道中写入数据:I Love Linux!。在readfifo读端,我们对管道进行读操作,最后printf打印出读到的数据。

首先,在后台运行writefifo写端程序:./writefifo testfifo &testfifo即为我们要创建的fifo管道,然后使用jobs检查是否在后台运行:

在这里插入图片描述

接着就可以运行读端的程序:

在这里插入图片描述

可以看到两个进程通信成功,读进程打印出了写进程写入管道的字符串!

FIFO命名管道可以让两个没有亲缘关系的进程进行通信,上述例子就是很好的证明。

同时FIFO管道是全双工通信的,速度上快于PIPE管道的半双工。

二、消息队列

消息队列的机制就像邮箱,当我们向消息队列中按照关键词、消息格式放入消息时,就相当于把我们写好的邮件按照姓名、地址等信息放到邮箱中。邮件到达后,接收方根据姓名等信息去取出属于自己的邮件,当我们按照类型读取消息队列中的消息也就是相当于接收邮件。

首先了解几个相关函数:

2.1 msgget创建/打开

int msgget(key_t key, int msgflg);

通过使用这个系统调用函数会返回一个与key关联的消息队列标识符。

key:关联词,创建的消息队列会与key关联起来,通过函数ftok获取:

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

这里pathname必须指向一个存在的可访问的文件,proj_id为工程id,大于0即可。

msgflg:标志位:

有两个选项IPC_CREAT和IPC_EXCL,单独使用IPC_CREAT,如果消息队列不存在则创建之,如果存在则打开返回;单独使用IPC_EXCL是没有意义的;两个同时使用,如果消息队列不存在则创建之,如果存在则出错返回。

返回:成功返回一个非负整型的队列标识符msqid,失败返回-1。

2.2 msgsnd发送

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数说明:

  • msqid:消息队列标识符,通过msgget()函数获取

  • msgp是用户定义的结构体,形式:

    //参数msgp
    struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[1];    /* message data */
    };
    
  • mtype成员是用来指定消息类型,进程选择消息类型时使用。必须为大于0的整数。

  • mtext一般是数组,手册中说也可以为结构体,这个成员用来存放消息。mtext的大小有msgsnd函数的参数msgsz指定,如果消息长度为0可以设置大小为0,但必须为非负整数。

  • msgsz即为消息的大小。

  • msgflg是一些标志:

    IPC_NOWAIT:如果消息队列中没有当前类型的消息不会等待,立即返回,并且置位errnoENOMSG

    MSG_EXCEPT:与大于0的msgtyp一起使用,读取队列中与msgtyp不同的消息类型的第一个消息。

    MSG_NOERROR:如果消息文本比msgsz字节长,则截断该消息文本。

    默认使用0。

  • 返回值:失败返回-1并且设置对应的errno,其他返回0。

2.3 msgrcv接收

 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

参数说明:

msgtyp对应msgp结构体中成员mtype,根据需要设置,设置为0则消息队列中第一个消息会被读出。其他参数同msgsnd函数。

返回值:接收成功返回得到的消息大小,失败返回-1,并且设置对应的errno

2.4 msgctl控制

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数:

msqid:由msgget函数返回的消息队列标识码。
cmd:有三个可选的值,在此我们使用IPC_RMID

  • IPC_STAT 把msqid_ds结构中的数据设置为消息队列的当前关联值。
  • IPC_SET 在进程有足够权限的前提下,把消息队列的当前关联词设置为msqid_ds数据结构中给出的值。
  • IPC_RMID 删除消息队列。

返回值: IPC_STAT, IPC_SET 和 IPC_RMID成功返回0,失败返回-1。

简单测试代码:

send.c:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//参数msgp的类型
struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[100];    /* message data */
};

//要发送的消息
char *data = "msg queue test!";

int main(int argc, char *argv[])
{
    key_t key;
    int msqid;
    struct msgbuf dataBuf; 

    key = ftok(".", 2020);//ftok获取关联词
    printf("key = 0x%x\n", key);
    msqid = msgget((key_t) key, 0666|IPC_CREAT);//创建消息队列
    printf("msqid = %d\n", msqid);
    if(msqid != -1)
    {   
        dataBuf.mtype = 1;//类型
        if(strcpy(dataBuf.mtext, data) != NULL)
        {   //数据拷贝成功
            if(msgsnd(msqid, &dataBuf, sizeof(dataBuf), 0) == -1){
                perror("msgsnd");
                exit(1);
            }
            else{
                printf("msgsnd success!\nsend data:%s\n", dataBuf.mtext);  
                exit(0);  
            }
        }
        else{
                perror("strcpy");
        }  
    }
    printf("msgsnd failed!\n");
    return -1;
}

send.c:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//参数msgp的类型
struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[100];    /* message data */
};

//要发送的消息
char *data = "msg queue test!";

int main(int argc, char *argv[])
{
    key_t key;
    int msqid;
    struct msgbuf dataBuf; 

    key = ftok(".", 2020);//ftok获取关联词
    printf("key = 0x%x\n", key);
    msqid = msgget((key_t) key, 0666|IPC_CREAT);//创建消息队列
    printf("msqid = %d\n", msqid);
    if(msqid != -1)
    {   
        dataBuf.mtype = 1;//类型
        if(strcpy(dataBuf.mtext, data) != NULL)
        {   //数据拷贝成功
            if(msgsnd(msqid, &dataBuf, sizeof(dataBuf), 0) == -1){
                perror("msgsnd");
                exit(1);
            }
            else{
                printf("msgsnd success!\nsend data:%s\n", dataBuf.mtext);  
                exit(0);  
            }
        }
        else{
                perror("strcpy");
        }  
    }
    printf("msgsnd failed!\n");
    return -1;
}

运行结果:

在这里插入图片描述

使用ipcs -q命令查看创建的消息队列:

在这里插入图片描述

消息队列中的消息被接收后会被清理,再次recv会阻塞等待消息,等待期间运行send可以发送消息到队列:

在这里插入图片描述

消息队列不会自动删除,可以使用ipcrm -q + msqid来删除一个消息队列,或者在程序中使用msgstl函数进行删除:

msgctl(msqid, IPC_RMID, 0);//返回0删除成功,返回-1出错

注意:使用windows下的子系统WSL可能无法创建消息队列,会报错!!!

三、信号

信号用于处理异步事件。

常见信号类型可以使用kill -l终端中查看,常见信号:

信号含义对进程的操作
SIGHUP该信号在用户终端关闭时产生,通常是发给和该终端关联的会话内的所有进程终止
SIGINT该信号在用户键入INTR字符(Ctrl+C)时产生,内核发送此信号送到当前终端的所有前台进程终止
SIGQUIT该信号和SIGINT类似,但由QUIT字符(通常是Ctrl+z)来产生终止
SIGILL该信号在一个进程企图执行一条非法指令时产生终止
SIGSEV该信号在非法访问内存时产生,如野指针、缓冲区溢出终止
SIGPIPE当进程往一个没有读端的管道中写入时产生,代表“管道断裂”终止
SIGKILL用来结束进程,并且不能被捕捉和忽略终止
SIGSTOP该信号用于暂停进程,用户可键入SUSP字符(通常是Ctrl+Z)发出这个信号暂停进程
信号含义操作
SIGCONT该信号让进程进入运行态继续运行
SIGALRM该信号用于通知进程定时器时间已到终止
SIGUSR1/2该信号保留给用户程序使用终止

信号发送

发送信号可以使用killraise函数,其中raise函数用于向自身进程发送信号,kill函数不仅可以向自身还可以向其他进程发送信号。

kill:

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

int kill(pid_t pid, int sig);

参数:

pid即为要发送的进程的id号,可以设置多种值,下面是man手册的说明:

  • 如果pid为正,则将信号sig发送给使用pid指定ID的进程。

  • 如果pid等于0,则sig被发送到正在调用的进程的进程组中的每个进程。

  • 如果pid等于-1,则sig被发送到调用进程有权发送信号的每个进程,进程1 (init)除外,但请参见下面。

  • 如果pid小于-1,则将sig发送给ID为-pid的进程组中的每个进程。

sig:如果sig为0,则不发送信号,但仍然执行错误检查; 可用于检查进程ID或进程组ID是否存在。

返回值:如果只有发送信号成功给一个进程,则返回0,否则返回-1,并且设置相应的error值。

raise:

#include <signal.h>

int raise(int sig);

man手册的描述:raise用于发送一个信号给调用的进程或线程,在单线程程序中,相当于kill(getppid(), sig);

这就说明了raise只能想自身发送信号的特性。

返回值:成功返回0,失败返回非0。

进程休眠/阻塞

使用pause函数可以暂停当前进行的进程,使其进入sleep休眠状态。直到发出一个终止进程信号,或者调用信号捕捉函数。我们可以借助pause来等待需要的信号。

#include <unistd.h>

int pause(void);

返回值:pause()仅在捕捉到信号并且信号捕捉函数返回时才返回。这中情况下,pause()返回-1,并将errno设置为EINTR。EINTR说明一个信号被捕捉到了,且捕捉函数已经返回。

信号处理

使用信号函数集来进行信号的处理。下面是一些简介,来自https://www.cnblogs.com/52php/p/5815125.html。

*1、int sigemptyset(sigset_t set);

该函数的作用是将信号集初始化为空。

*2、int sigfillset(sigset_t set);

该函数的作用是把信号集初始化包含所有已定义的信号。

*3、int sigaddset(sigset_t set, int signo);

该函数的作用是把信号signo添加到信号集set中,成功时返回0,失败时返回-1。

*4、int sigdelset(sigset_t set, int signo);

该函数的作用是把信号signo从信号集set中删除,成功时返回0,失败时返回-1。

*5、int sigismember(sigset_t set, int signo);

该函数的作用是判断给定的信号signo是否是信号集中的一个成员,如果是返回1,如果不是,返回0,如果给定的信号无效,返回-1;

**6、int sigprocmask(int how, const sigset_t set, sigset_t oset);

该函数可以根据参数指定的方法修改进程的信号屏蔽字。新的信号屏蔽字由参数set(非空)指定,而原先的信号屏蔽字将保存在oset(非空)中。如果set为空,则how没有意义,但此时调用该函数,如果oset不为空,则把当前信号屏蔽字保存到oset中。

*7、int sigpending(sigset_t set);

该函数的作用是将被阻塞的信号中停留在待处理状态的一组信号写到参数set指向的信号集中,成功调用返回0,否则返回-1,并设置errno表明错误原因。

8、int sigsuspend(const sigset_t *sigmask);

该函数通过将进程的屏蔽字替换为由参数sigmask给出的信号集,然后挂起进程的执行。注意操作的先后顺序,是先替换再挂起程序的执行。程序将在信号处理函数执行完毕后继续执行。如果接收到信号终止了程序,sigsuspend()就不会返回,如果接收到的信号没有终止程序,sigsuspend()就返回-1,并将errno设置为EINTR。

alarm闹钟/定时

alarm其实应该归于产生信号的函数,用于在一段时间后产生一个SIGALRM信号,如果不对这个信号捕捉处理,默认会终止进程。

创建alarm再次alarm(n),n会覆盖前面创建时的值,如果n为0则取消闹钟。

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

seconds为设置的闹钟时间,单位为秒。设置为0,则取消闹钟并返回当前闹钟剩余的时间。

返回值:创建闹钟成功返回0。

简单示例:

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


int main(int argc, char *argv[])
{   
    alarm(10);
    sleep(1);
    printf("the remain time is: %ds\n", alarm(5));
    sleep(2);
    printf("the remain time is: %ds\n", alarm(0));   
    return 0;
}

运行结果:

在这里插入图片描述

signal处理信号

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

参数:

signum:信号类型。

handler:信号到达之后执行的回调函数。

返回值:回调函数类型的函数指针。

简单使用:

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

void callback()
{
    printf("this is a signal test!\n");
}

int main(int argc, char *argv[])
{
    signal(SIGALRM, callback);
    alarm(3);
    for(char i=0; i<3; i++)
    {
        sleep(1);
        printf("sleep 1s!\n");
    }
    sleep(5);
    printf("finish!\n");
    return 0;
}

执行结果:

signal

sigaction

#include <signal.h>

int sigaction(int signum, const struct sigaction *act,
              struct sigaction *oldact);

sigaction也是对信号进行处理的函数,这个函数比较复杂。man手册对其描述如下:

sigaction()系统调用用于更改进程在接收到特定信号时所采取的操作。(信号概述见信号(7))

signum指定信号,并且可以是除SIGKILL和SIGSTOP之外的任何有效信号。

如果act是非空的,则从act安装signal signum的新操作。如果oldact是非空的,那么之前的操作将保存在oldact中。

看一下sigaction结构体:

struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

结构体成员的说明:

在一些体系结构中涉及到一个联合:不要同时分配给sa_handler和sa_sigaction。

sa_restorer字段不打算用于应用程序。(POSIX没有指定一个sa_restorer字段。)这个领域的一些进一步的细节可以是发现sigreturn (2)。

sa_handler指定与signum相关联的操作,对于默认操作可能是SIG_DFL, SIG_IGN忽略这个信号,或者是一个指向信号处理函数的指针。这个函数接收信号号作为它的唯一参数。

如果sa_flags中指定了SA_SIGINFO,那么sa_sigaction(而不是sa_handler)指定了signum的信号处理函数。这个函数接收第一个参数是信号号,第二个参数是指向siginfo_t的指针,第三个参数是指向ucontext_t(强制转换为void *)的指针。(通常,处理函数不使用第三个参数。有关ucontext_t的更多信息,请参见getcontext(3)。)

sa_mask指定在执行信号处理程序期间应阻止(即,添加到调用信号处理程序的线程的信号掩码中)的信号掩码。 另外,除非使用SA_NODEFER标志,否则触发处理程序的信号将被阻止。

示例:

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

void handler(int sig)
{
    printf("\nsig = %d\n", sig);
}

int main(int argc, char *argv[])
{
    sigset_t sigset;//记录信号屏蔽字
    struct sigaction act;
    //1.
    act.sa_handler  = handler;//设置信号处理函数
    sigemptyset(&act.sa_mask);//信号掩码设置为空
    act.sa_flags = 0;//
    sigaction(SIGINT, &act, 0);//指定信号为中断,绑定新的信号处理操作为act
    
    printf("wait the signal SIGINT ...\n");
    pause();
    //2.
    sigemptyset(&sigset);//清空信号集
    sigaddset(&sigset, SIGINT);//添加SIGINT信号到信号集sigset
    sigprocmask(SIG_SETMASK, &sigset, 0);//修改信号屏蔽字为sigset
    printf("Please input Ctrl+c in 6 seconds...\n");
    sleep(6);

    if(sigismember(&sigset, SIGINT))//检查SIGINT是否为ign信号集的成员
        printf("\nThe SIGINT signal has been ignored in sigset\n");
    //3.
    if(sigdelset(&sigset, SIGINT) == 0)
    {//删除信号SIGINT成功
        printf("\nDel SIGINT in sigset success!\n");
        if(sigismember(&sigset, SIGINT) == 0)//检查SIGINT是否为ign信号集的成员
            printf("\nThe SIGINT signal has been delete in sigset\n");
    }

    sigprocmask(SIG_SETMASK, &sigset, 0);//修改信号屏蔽字为sigset
    printf("Please input Ctrl+c to test!\n");
    pause();

    printf("test finish!\n");
    exit(0);  
}

运行结果:

在这里插入图片描述

  1. 在程序开头使用sigaction绑定了一个中断处理操作,SIGINT信号对应的处理函数通过结构体act绑定为handler()函数。当我们在键盘上按下Ctrl+c就会产生SIGINT信号,程序接收到这个信号在handler中进行处理,打印出信号的对应值。

  2. 接着我们使用函数sigprocmask将sigset信号集中的信号设置到进程的屏蔽信号集中,因为信号集sigaddset(&sigset, SIGINT);加入了SIGINT,所以设置的屏蔽字中会包括SIGINT信号。在接下来6s中输入ctrl+c信号并不会响应信号进行处理。

  3. 最后我们再删除SIGINT信号并且pause进程,输入Ctrl+c信号进行测试。因为此时已经不再屏蔽SIGINT信号,所以会打断pause继续运行。

从结果可以看到当我们在屏蔽信号集删除了SIGINT信号后,之前的信号Ctrl+c还会继续处理。

注意:每次修改sigset_t的信号集,都要使用sigprocmask()函数将需要屏蔽的信号集更新才会生效。

使用kill命令来进行通信:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
void handler(int sig)
{
	if(sig==SIGINT)
		printf("get SIGINT, sig = %d\n", sig);
	else if(sig==SIGQUIT)//
		printf("get SIGQUIT, sig =%d\n", sig);
    else if(sig == SIGSTOP)
        printf("get SIGSTOP, sig = %d\n", sig);
}
 
int main()
{
	printf("Waiting for signal!!\n");
    printf("usage: kill -s signame pid \n");
	//注册新号处理函数
	signal(SIGINT,handler);
	signal(SIGQUIT,handler);
    signal(SIGSTOP, handler);

	pause();
	exit(0);
}

后台运行上面的程序,使用kill -s +信号名称+进程id向进程发送信号,kill也是一个系统调用,可以查看man手册的使用方法。

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值