linux进程间通信 管道, 消息队列, 共享内存 信号(一)

进程间通信

之前进程之间交换信息的方法只能是经由fork或exec传送打开文件,或者通过文件系统。
下来将说明进程之间相互通信的其他技术—IPC(interProcess Commumication)
进程间通信IPC
IPC引入
IPC的方式通常有管道(包括无名管道和命名管道),消息队列,信号量,共享存储,Socket,Streams等,其中Socket和Streams支持不同主机上的两个进程IPC
一,管道
1.特点

  1. 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端
  2. 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
  3. 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read,write等函数,但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存

2原理
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。管道的建立存在与内核之中,如下图:
在这里插入图片描述
要关闭管道只需将这两个文件描述符关闭即可
在这里插入图片描述
3创建
单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:
在这里插入图片描述
创建管道
#include <unistd.h>
int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1

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

//int pipe(int pipefd[2]);
//int pipe(int fd[2]);

int main()
{
    
    
        int pid;
        char buf[128];
        int fd[2];
        if(pipe(fd) == -1)//建立管道      
         {
    
    
                printf("pipe create failed\n");
        }

        pid = fork();//建立子进程
        if(pid < 0)//建立子进程失败
        {
    
    
                printf("the child create failed\n");
        }
        else if(pid > 0)//父进程运行空间,pid>0时
        {
    
    
                printf("this is father\n");
                close(fd[0]);//父进程关闭读端文件描述符
                write(fd[1], "hello world", strlen("hello world\n"));//父进程对管道内写入
        }
        else if(pid == 0)//子进程运行空间,pid=0时
        {
    
    
                printf("this is child\n");
                close(fd[1]);//子进程关闭写端文件描述符
                read(fd[0], buf, 128);//子进程从管道内读出数据到buf
                printf("%s\n", buf);
        }
        return 0;
}

结果如下
在这里插入图片描述

FIFO 进程间通信第二种方式

特点
(1)FIFO可以在无关的进程之间交换数据,与无名管道不同。
(2)FIFO 有路径名(无名管道没有)与之相关联,它以一种特殊设备文件形式存在于文件系统中。
(3)管道中的数据被读走就没了,同时保持FIFO(先进先出的特点。
原理
与无名管道类似。
创建
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);// 返回值:成功返回0,出错返回-1
pathname是函数的名字
其中的 mode 参数与open函数中的 mode 相同(0600-读写)(open函数)。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,==是否设置非阻塞标志(O_NONBLOCK)==的区别:

若没有指定O_NONBLOCK(默认)

只读open 要阻塞到某个其他进程为写而打开此 FIFO。
只写open 要阻塞到某个其他进程
为读**而打开它。
若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO

实例

read.c

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

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

int main()
{
    
    
        char buf[128];
        if((mkfifo("./file",0600)==-1) && errno!=EEXIST)//建立管道
        {
    
    
                printf("mkfifo failure\n");
                perror("why");
        }
        else  if(errno==EEXIST)
        {
    
    
                printf("file eexist\n");
        }
        else
        {
    
    
                printf("mkfifo successed\n");
        }
        int fd = open("./file", O_RDONLY);
        //打开管道,O_RDINLY-只读模式打开,如果没有另一个进程以只写模式打开管道,程序会阻塞此处
        int nread = read(fd, buf, 20);//从管道内读数据
        printf("read %d byte from fifo,context:%s\n",nread,buf);
        close(fd);//关闭管道
        return 0;
}

write.c

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include <errno.h>
#include <fcntl.h>
int main()
{
    
    
// ssize_t read(int fd, void *buf, size_t count);
        char *buf="hello world !!!!!!!!";
        int fd = open("./file",O_WRONLY);
        //打开管道,O_WRONLY-只写模式打开,如果没有另一个进程读它,程序会阻塞此处
        printf("write file success\n");
        write(fd,buf,strlen(buf));写入数据
       return 0;
}
~     

在这里插入图片描述
读的时候 不写那么读就会阻塞,知道写执行,才会读
上述例子可以扩展成 客户进程—服务器进程 通信的实例,
write的作用类似于客户端,可以打开多个客户端向一个服务器发送请求信息,
read类似于服务器,它适时监控着FIFO的读端,当有数据时,读出并进行处理,
每一个客户端必须预先知道服务器提供的FIFO接口,如下图。
在这里插入图片描述

消息队列的通信原理

消息队列(message queue)

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

特点

(1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级(链表存放的为结构体)。
(2)消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除(管道是读完就消失),除非销毁队列。
(3)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取(链表的特性),也可以按消息的类型读取。
(4)没有固定的读端与写端,
双方进程都可以

原理

在这里插入图片描述

创建

了解了原理,我们则发现主要关心两个问题。
问题一:进程A如何添加消息队列
问题二:进程B如何读取消息队列

常用的api

1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);

4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);

6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);

8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

10//系统IPC键值的格式转换函数 
11 key_t ftok( const char * fname, int id )

msgget()

创建或打开消息队列:成功返回队列ID,失败返回-1

int msgget(key_t key, int flag);

在以下两种情况下,msgget将创建一个新的消息队列:
1、如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。

msgget(key,IPC_CREAT);

2、key参数为IPC_PRIVATE

msgget(key,IPC_PRIVATE);

msgsnd()

添加消息:成功返回0,失败返回-1

int msgsnd(int msqid, const void *ptr, size_t size, int flag);

msqid:消息队列的ID
ptr:写入的数据,指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下:

struct msgbuf {
    
    
	long mtype; /* 消息类型,必须 > 0 */
	char mtext[1]; /* 消息文本 */
};

size:数据的长度
flag:0,表示忽略,表示进程将被阻塞直到函数可以从队列中得到符合条件的消息为止;(还有许多,此处省略)

msgrcv()

读取消息:成功返回消息数据的长度,失败返回-1

int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);

msqid:消息队列的ID

ptr:写入的数据,指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下:

struct msgbuf {
    
    
	long mtype; /* 消息类型,必须 > 0 */
	char mtext[1]; /* 消息文本 */
};

type:消息类型
msgtyp等于0 则返回队列的最早的一个消息。
msgtyp大于0,则返回其类型为msgtyp的第一个消息。
msgtyp小于0,则返回其类型小于或等于mtype参数的绝对值的最小的一个消息。
可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。
size:数据的长度
flag:0,表示忽略,表示进程将被阻塞直到函数可以从队列中得到符合条件的消息为止;(还有许多,此处省略)

msgctl()

控制消息队列:成功返回0,失败返回-1

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msqid:消息队列的ID

cmd:函数要对消息队列进行的操作,它可以是:
IPC_STAT
取出系统保存的消息队列的msqid_ds 数据,并将其存入参数buf 指向的msqid_ds 结构
中。
IPC_SET
设定消息队列的msqid_ds 数据中的msg_perm 成员。设定的值由buf 指向的msqid_ds
结构给出。
IPC_EMID
将队列从系统内核中删除。
例:msgctl(msgid,IPC_RMID,NULL);//将队列从系统内核中删除
buf:队列中的内容

ftok()

系统IPC键值的格式转换函数,系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。

key_t ftok( const char * fname, int id );

fname:就是你指定的文件名(已经存在的文件名),一般使用当前目录,如

key_t key;
key = ftok(".", 1);

这样就是将fname设为当前目录。
id:是子序号。虽然是int类型,但是只使用8bits(1-255)。
如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。

查询文件索引节点号的方法是: ls -i
当删除重建文件后,索引节点号由操作系统根据当时文件系统的使用情况分配,因此与原来不同,所以得到的索引节点号也不同。
如果要确保key_t值不变,要么确保ftok的文件不被删除,要么不用ftok,指定一个固定的key_t值,比如:

#define IPCKEY 0x111
char path[256];
sprintf( path, "%s/etc/config.ini", (char*)getenv("HOME") );
msgid=ftok( path, IPCKEY );

同一段程序,用于保证两个不同用户下的两组相同程序获得互不干扰的IPC键值。
由于etc/config.ini(假定)为应用系统的关键配置文件,因此不存在被轻易删除的问题——即使被删,也会很快被发现并重建(此时应用系统也将被重启)。
ftok()的设计目的也在于此

使用实例

msgGet.c

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

struct msgbuf {
    
    
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};

int main()
{
    
    
        struct msgbuf readbuf;
        //int msgget(key_t key, int flag);
        int msgId = msgget(0x1234,IPC_CREAT|0777);
        if(msgId == -1)
        {
    
    
                printf("create duilie failed\n");
        }

        //int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
        // ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);
        printf("read from que: %s\n", readbuf.mtext);
        return 0;
}

msgSend.c

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
    
    
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};

int main()
{
    
    
        struct msgbuf sendbuf= {
    
    888, "this is message from quen"};
        //int msgget(key_t key, int flag);
        int msgId = msgget(0x1234,IPC_CREAT|0777);
        if(msgId == -1)
        {
    
    
                printf("create duilie failed\n");
        }

        //int msgsnd(int msqid, const void *ptr, size_t size, int flag);
        msgsnd(msgId, &sendbuf, strlen(sendbuf.mtext),0 );


        return 0;
}

结果如下所示

在这里插入图片描述

实现互相发送信息代码

msgGet.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
struct msgbuf{
    
    
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
int main()
{
    
    
        struct msgbuf sendbuf={
    
    999,"888 message already received"};
        struct msgbuf readbuf;
        int msgid = 0;
        key_t key;
        key = ftok(".",'z');//获取键值
        printf("key=%x\n",key);
        msgid=msgget(key,IPC_CREAT|0777);//在内核中打开或建立键值为key的,权限为0777的消息队列
        if(msgid == -1){
    
    
                printf("create msgq failure\n");
        }
        msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),888,0);//从队列中获取888类型的数据,如果队列中未出现888类型的数据,则程序阻塞在这里
        printf("read from que:%s\n",readbuf.mtext);
        msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);//往队列id为msgid的队列写入sendbuf(类型为999)数据
        msgctl(msgid,IPC_RMID,NULL);//将队列从系统内核中删除
        return 0;
}

msgSend.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
struct msgbuf{
    
    
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
int main()
{
    
    
        struct msgbuf sendbuf={
    
    888,"this is message from que"};
        struct msgbuf readbuf;
        int msgid= 0;
        key_t key;
        key = ftok(".",'z');//获取键值
        printf("key=%x\n",key);
        msgid=msgget(key,IPC_CREAT|0777);//在内核中打开或建立键值为key的,权限为0777的消息队列
        if(msgid == -1){
    
    
                printf("create msgq failure\n");
        }
        msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);//往队列id为msgid的队列写入sendbuf(类型为888)数据
        msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),999,0);//从队列中获取999类型的数据,如果队列中未出现999类型的数据,则程序阻塞在这里
        printf("%s\n",readbuf.mtext);
        msgctl(msgid,IPC_RMID,NULL);//将队列从系统内核中删除
        return 0;
}

在这里插入图片描述
1、运行send函数,打开键值为的队列,因为队列里没有888类型的数据,程序阻塞,等待
2、运行read函数,打开键值为的队列,往队列里写入888类型的数据,接收端send读取到888类型数据,同时接收端往队列里写入999类型的反馈信息,发送端从队列中读取999类型的信息。

共享内存

共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区

特点

共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
因为多个进程可以同时操作,所以需要进行同步。
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问

原理

在这里插入图片描述

步骤

1、创建共享内存
2、进程A连接共享内存,写入数据
3、进程A断开连接
4、进程B连接共享内存,读取数据
5、进程B断开连接
6、释放公共内存

创建

常用API

1 #include <sys/shm.h>
2
3 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
4 int shmget(key_t key, size_t size, int flag);
5
6 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
7 void *shmat(int shm_id, const void *addr, int flag);
8
9 // 断开与共享内存的连接:成功返回0,失败返回-1
10 int shmdt(void *addr); 
11
12 // 控制共享内存的相关信息:成功返回0,失败返回-1
13 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

常用API的详细解析引用于linux进程间通信使用共享内存

1、shmget函数

功能

该函数用来创建共享内存
参数原型

int shmget(key_t key, size_t size, int shmflg);

参数

key:与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,

不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget函数的返回值),只有shmget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。

size:size以字节为单位指定需要共享的内存容量

shmflg:shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。

返回值

shmget函数成功时返回一个与key相关的shm_id共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.

2、shmat函数

功能

第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。

参数原型
void *shmat(int shm_id, const void *shm_addr, int shmflg); 

参数

shm_id:是由shmget函数返回的共享内存标识。
*shm_addr:指定共享内存连接到当前进程中的地址位置,通常为空(为0),表示让系统来选择共享内存的地址。
shm_flg:是一组标志位,通常为0。

返回值

调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.

3、shmdt函数

功能

该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。

参数原型:

int shmdt(const void *shmaddr); 

参数

*shmaddr:是shmat函数返回的地址指针。

返回值

调用成功时返回0,失败时返回-1.

4、shmctl函数

功能

控制共享内存

参数原型:

int shmctl(int shm_id, int command, struct shmid_ds *buf);  

参数

shm_id:shm_id是shmget函数返回的共享内存标识符。

command:command是要采取的操作,它可以取下面的三个值 :
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID:删除共享内存段

*buf:buf是一个结构指针,它指向共享内存模式和访问权限的结构。
shmid_ds结构至少包括以下成员:

struct shmid_ds  
{
    
      
    uid_t shm_perm.uid;  
    uid_t shm_perm.gid;  
    mode_t shm_perm.mode;  
};  

linux编译退出一般异常是exit(-1), 正常是exit(0)也不是绝对的

实例

write.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdio.h>
#include<string.h>
#include <stdlib.h>
int main()
{
    
    
        int shmid=0;
        char *shmaddr=NULL;
        key_t key;
        key = ftok(".",1);
        shmid=shmget(key,1024*4,IPC_CREAT|0666);//创建共享内存,权限为可读可写可执行
        if(shmid==-1){
    
    
                printf("shmget failure!\n");
                exit(-1);
        }
        shmaddr=shmat(shmid,0,0);//连接共享内存
        printf("shmaddr link ok!\n");
        strcpy(shmaddr,"aipolo");//写入数据
        sleep(5);
        shmdt(shmaddr);//断开连接
        shmctl(shmid,IPC_RMID,0);//关闭共享内存
        return 0;
}

read.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdio.h>
#include<string.h>
#include <stdlib.h>
int main()
{
    
    
        int shmid=0;
        char *shmaddr=NULL;
        key_t key;
        key = ftok(".",1);

        shmid=shmget(key,1024*4,0);//打开共享内存
        if(shmid==-1){
    
    
                printf("shmget failure!\n");
                exit(-1);
        }
        shmaddr=shmat(shmid,0,0);//连接共享内存
        printf("shmaddr link ok!\n");
        printf("data:%s\n",shmaddr);//打印数据
        shmdt(shmaddr);//断开连接
        printf("quit\n");
        return 0;
}

在这里插入图片描述

信号

信号的原理

对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了ctrl+c来中断程序,会通过信号机制停止一个程序。

信号的特点

简单 不能携带大量信息 , 满足某个特设条件才发送
一个完整的信号周期包含3个部分,:
信号的产生,
信号在进程的注册,
信号在进程中的注销,
执行信号处理函数

信号概述

1、信号的名字和编号:

每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。信号定义在 signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以 使用kill -l查看信号 的名字以及序号
信号是 从1开始编号的,不存在0号信号。kill对于信号0有特殊的应用
在这里插入图片描述
不存在为0的信号,其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关,名字上区别不大,而前32个名字各不相同
信号的4要素: 分别是
1)编号 2)名称 3)事件 4)默认处理动作 可通过man 7 signal查看帮助文档

2、信号的处理:

信号的处理有三种方法,分别是:忽略捕捉默认动作

忽略信号

大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景。
SIG_IGN为系统自带的宏函数。例:

signal(SIGINT,SIG_IGN);//将SIGINT信号(ctrl+C、2)忽略

捕捉信号

需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。

系统默认动作

对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。也可以参考 《UNIX 环境高级编程(第三部)》的 P251——P256中间对于每个信号有详细的说明。
了解了信号的概述,那么,信号是如何来使用呢?

其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。

kill -9 进程PID
kill -SIGKILL 进程PID

在这里插入图片描述
在这里插入图片描述
对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通讯的手段,那么如何来自定义信号的处理函数呢?

实战信号编程

创建

常用API

信号处理函数的注册

入门版:函数signal
高级版:函数sigaction

信号处理发送函数

1.入门版:kill
2.高级版:sigqueue

在正式开始了解这两个函数之前,可以先来思考一下,处理中断都需要处理什么问题。

按照我们之前思路来看,可以发送的信号类型是多种多样的,每种信号的处理可能不一定相同,那么,我们肯定需要知道到底发生了什么信号。
另外,虽然我们知道了系统发出来的是哪种信号,但是还有一点也很重要,就是系统产生了一个信号,是由谁来响应?
如果系统通过 ctrl+c 产生了一个 SIGINT(中断信号),显然不是所有程序同时结束,那么,信号一定需要有一个接收者。对于处理信号的程序来说,接收者就是自己。
signal函数
signal 的函数原型

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

函数原型由两部分组成,一个是真实处理信号的函数,另一个是注册函数了。
注册函数:
==sighandler_t signal(int signum, sighandler_t handler);==函数来说,
signum :信号的编号。
handler :中断函数的指针。

handler函数

真实处理信号的函数:
==typedef void (*sighandler_t)(int);==中断函数的原型中,有一个参数是 int 类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。
我们先来看看简单一个信号注册的代码示例吧。

实例1:信号的处理:捕捉动作

domo1.c

信号处理函数的注册

#include<stdio.h>
#include <signal.h>
//真实处理信号的函数
void handler(int signum)
{
    
    
        printf("get signum=%d\n",signum);
        switch(signum){
    
    
                case 2:
                        printf("SIGINT\n");
                        break;
                case 10:
                        printf("SIGUSR1\n");
                        break;
        }
}
int main()
{
    
    
        signal(SIGINT,handler);//信号处理函数的注册
        signal(SIGUSR1,handler);//信号处理函数的注册
        while(1);
        return 0;
}

domo2.c

demo2主要是将kill信号处理发送函数置于程序中,同时也可以使用system来实现kill。
信号处理发送函数:

#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc,char **argv)
{
    
    
        int signum=0;
        int pid=0;
        char cmd[128]={
    
    0};//设置system函数处理命令的大小
        signum= atoi(argv[1]);//转为整型
        pid= atoi(argv[2]);//转为整型
        printf("num=%d\n",signum);
        printf("pid=%d\n",pid);
       	
		kill(pid,signum);
		// sprintf(cmd,"kill -%d %d",signum,pid);
        // system(cmd);
        printf("send signal ok\n");
        return 0;
}

注:
1、atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数。
2、sprintf指的是字符串格式化命令,
函数声明为 int sprintf(char *string, char *format [,argument,…]);,
主要功能是把格式化的数据写入某个字符串中,即发送格式化输出到 string 所指向的字符串。
3、

printf(cmd,"kill -%d %d",signum,pid);
system(cmd);

也可以实现kill(pid,signum);的功能

实验结果:

domo1实现:在这里插入图片描述

在这里插入图片描述

domo2实现:

demo2主要是将kill信号处理发送函数置于程序中,同时也可以使用system来实现kill。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

实例2:信号的处理:忽略

#include<stdio.h>
#include <signal.h>
void handler(int signum)
{
    
    
        printf("get signum=%d\n",signum);
        switch(signum){
    
    
                case 2:
                        printf("SIGINT\n");
                        break;
                case 10:
                        printf("SIGUSR1\n");
                        break;
        }

}
int main()
{
    
    
        signal(SIGINT,SIG_IGN);//将SIGINT信号(ctrl+C、2)忽略
        signal(SIGUSR1,SIG_IGN);//将SIGUSR1信号(10)忽略

        while(1);


        return 0;
}


实验结果:

会发现ctrl+c与kill -10 pid已经对进程不起作用了,这两个信号被进程忽略。

在这里插入图片描述
linux进程间通信信号

下面不是老陈的

发起信号的方式:

a) 当用户按某些终端键时,将产生信号
b)硬件异常将产生信号,除数为0,无效的内存访问等
c)软件异常将产生信号(定时器)
d)调用系统函数(如:kill, raise, abort)将发送信号
e)运行kill/killall命令将发送信号

信号的API

kill函数

#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid, int sig);

功能:给指定进程发送指定信号(不一定杀死)
参数:
pid : 取值有4种情况:
pid> 0 : 将信号传送给进程ID为pid的进程
pid= 0 : 将信号传送给当前进程所在进程组中的所有进程
pid= -1: 将信号传送给系统内所有的进程
pid< -1: 将信号传给指定进程组的所有进程,这个进程组号等于pid的绝对值
sig:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令kill - l("l"为字母) 进行相应查看,不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致
返回值:
失败:0
成功: 1
在这里插入图片描述

kill函数 实例使用

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

int main()
{
    
    
        pid_t pid = fork();

        if(pid == 0)
        {
    
    
                while(1)
                {
    
    
                        printf("-pid ----- play  computer   games%d\n", getpid());
                        sleep(1);
                }
                exit(-1);
        }
        else if(pid > 0)
        {
    
    
                printf("you hava five times \n");
                sleep(5);
                kill(pid, 9);
                wait(NULL);
        }
        return 0;
}

在这里插入图片描述

2. raise函数

#include<signal.h>
int raise(int sig);

在这里插入图片描述

raise实例

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

int main()
{
    
    
        int i;
        while(1)
        {
    
    
                 printf("five time over jincheng \n");
                 sleep(5);
                 raise(SIGINT); //这里也可以是3
                 //因为SIGINT 也就是信号3是 CTRL+C
                 printf("0-------------\n");
        }
        return 0;
}
~                   

确实在五秒后结束了
在这里插入图片描述

abort函数

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

alarm函数(闹钟)

#include<unistd.h>
unsigned int alarm(unsigned int seconds);

在这里插入图片描述

在这里插入图片描述
总共定时7秒,先5秒过了2秒又定5秒,所以是 7秒

修改信号的处理动作

信号处理方式 一个进程收到一个信号的时候,可以用如下方法进行处理,
(1)执行系统默认动作,对大多数信号来说,系统默认动作是用来终止该进程
(2)忽略此信号(丢弃)接收到此信号后没有任何动作
(3)执行自定义信号处理函数(捕获)用用户定义的信号处理函数,处理该信号
注意:SIGKILL(9)SIGSTOP(10不能更改信号的处理方式,因为它们向用户提供了一种使进程中止的可靠方法

给信号注册,自定义函数

捕捉信号并且信号的处理方式有两个函数signalsigaction

signal函数

#include<signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

功能:
注册信号函数(不可用于 SIGKILL , SIGSTOP 信号),即确定收到信号后处理函数的入口地址,此函数不会阻塞
参数:
signum: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令kill-l进行相应查看
handler: 取值有3种情况:
SIG_IGN: 忽略该信号
SIG_DFL: 执行系统默认动作
信号处理函数名:自定义信号处理函数,如func
回调函数的定义如下

void func(int sign)
{
    
    
	//sign为触发的信号,为signal()第一个参数的值
}

返回值:
成功:第一次返回NULL,下一次返回此信号上一次注册的信号处理函数的地址,如果需要使用此返回值,必须在前面先声明此函数指针的类型
失败:返回SIG_ERR

猜你喜欢

转载自blog.csdn.net/weixin_52495715/article/details/121698412
今日推荐