比如手机上的微信跟别人手机上的微信聊天,它通信的方式是基于网络的
1、无名管道
2、命名管道
3、消息队列
4、共享内存
5、信号
6、信号量
目的介绍:
进程A,进程B之间应该建立一个特殊的通道,进程A能往里写数据,进程B能读里面的数据,进程B能往里面写数据,进程A能读里面的数据,让它们之间实现真正的数据交互
进程A,进程B放在一台PC就是单机通信,如果进程B放在另一台PC那就的多机之间的进间通信,而它们之间就是通过网来通信的
父子进程的基础版本进程间通信:
父进程运行到某个位置,创建一个子进程,子进程有它的数据空间,父进程,子进程的数据空间是相互独立的。
相互之间父进程不能读取到子进程的数据,子进程也不能读到父进程的数据,它们之间缺少一种方式沟通。
残疾的通信方式:
子进程调用exit(0);退出,父进程调用wait(&status);来等待子进程运行结束,在获取子进程的退出码,这也算一种意义上进程间的通信。只不过这种进程间同信在运行中间的意义不大,因为这也只有exit函数把退出码返回发给父进程而已。
exec族函数也可以做到用A进程启动一个B进程,A就不运行,也可以实现简陋基础的信息沟通
这两个方法都是基于fork,exec来实现的,这些方式都相对于真正意义上的通信比较残疾,而真正能很好实现进程间通信的技术是IPC。
是指在不同进程之间传播或交换信息
以前,UNIX系统IPC是各种进程通信方式的统称,但是其中极少能在所有UNIX系统实现中进行移植,随着POSIX和Open Group(以前的X/Open)标准化的推进和影响的扩大,情况虽已得到改善,但差别仍然存在。
IPC类型 | SUS | FreeBSD 5.2.1 | Linux 2.4.22 | Mac OS X 10.3 | Solaris 9 |
---|---|---|---|---|---|
半双工管道 | · | (全) | · | · | (全) |
FIFO | · | · | · | · | · |
全双工管道 | 允许 | ·,UDS | opt,UDS | UDS | ·,UDS |
命名全双工管道 | XSI可选 | UDS | opt,UDS | UDS | ·,UDS |
消息队列 | XSI | · | · | · | |
信号量 | XSI | · | · | · | · |
共享存储 | XSI | · | · | · | · |
套接字 | · | · | · | · | · |
STREAMS | XSI可选 | opt |
管道(包括无名管道和命名管道)、消息队列、信号量、共享存储都是基于单机的通信
Socket、Streams是基于网络的通信
以Linux中的C语言编程为例。
管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。
1、特点:
2、原型:
#include
int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:
read(fd[0], readBuf, 1024)
write(fd[1], readBuf, 1024)
3、例子
单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:
若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。
示例代码:
#include<stdio.h>
#include<unistd.h>
int main()
{
int fd[2]; // 两个文件描述符
pid_t pid;
char buff[20];
if(pipe(fd) < 0) // 创建管道
printf("Create Pipe Error!\n");
if((pid = fork()) < 0) // 创建子进程
printf("Fork Error!\n");
else if(pid > 0) // 父进程
{
close(fd[0]); // 关闭读端
write(fd[1], "hello world\n", 12);
}
else
{
close(fd[1]); // 关闭写端
read(fd[0], buff, 20);
printf("%s", buff);
}
return 0;
}
无名管道阻塞案例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
//int pipe(int pipefd[2]);
int main()
{
int fd[2];
pid_t pid;
char buf[128];
if(pipe(fd) == -1){
printf("creat pipe failed!\n");
}
pid = fork();
if(pid < 0){
printf("creat child failed!\n");
}else if(pid > 0){
sleep(3);
printf("this is father\n");
close(fd[0]);
write(fd[1], "hello from father!", strlen("hello from father!"));
wait(NULL);
}
else{
printf("this is child\n");
close(fd[1]);
read(fd[0], buf, 128);
printf("read from father:%s\n",buf);
exit(0);
}
return 0;
}
FIFO,也称为命名管道,它是一种文件类型。
#include
// 返回值:成功返回0,出错返回-1
int mkfifo(const char *pathname, mode_t mode);
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
创建FIFO管道:
#include <sys/types.h>
#include <sys/stat.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
mkfifo("./file",0600);
return 0;
}
判断要创建的FIFO文件存在的代码优化:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
int ret = mkfifo("./file",0600);
if(ret == 0){
printf("mkfifo suscceess\n");
}
if(ret == -1){
printf("mkfifo failuer\n");
perror("why");
}
return 0;
}
判断优化:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
if((mkfifo("./file",0600) == -1) && errno == EEXIST ){
printf("mkfifo failuer\n");
perror("why");
}
else{
if(errno == EEXIST){
printf("This file exists!\n");
}else{
printf("mkfifo suscceess\n");
}
}
return 0;
}
只读打开:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
if((mkfifo("./file",0600) == -1) && errno != EEXIST ){
printf("mkfifo failuer\n");
perror("why");
}
int fd = open("./file",O_RDONLY);
printf("open success\n");
close(fd);
return 0;
}
这样打开FIFO管道文件后,下一行open success不会执行,管道会阻塞,要有另一个程序以只写入的方式打开,才会接着执行
只写打开:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
int fd = open("./file",O_WRONLY);
printf("write open success\n");
close(fd);
return 0;
}
当只读打开FIFO文件程序运行后,只写打开FIFO文件程序在运行后,只读打开FIFO文件程序才会从阻塞状态中结束后接着运行
只读方式打开FIFO文件:
gcc read.c -o read
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
char buf[30] = {0};
if((mkfifo("./file",0600) == -1) && errno != EEXIST ){
printf("mkfifo failuer\n");
perror("why");
}
int fd = open("./file",O_RDONLY);
printf("open success\n");
int n_read = 0;
int cnt = 0;
while(1){
n_read = read(fd, buf, 30);
printf("read %d byte from fifo, context = %s\n",n_read,buf);
if(cnt == 5){
break;
}
cnt++;
}
close(fd);
return 0;
}
只写方式打开FIFO文件:
gcc write.c -o write
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
int cnt = 0;
char *str = "message from fifo!";
int fd = open("./file",O_WRONLY);
printf("write open success\n");
while(1){
write(fd, str, strlen(str));
sleep(1);
if(cnt == 5){
break;
}
cnt++;
}
close(fd);
return 0;
}
这样就完成了基本的单向进程间的通信了
上述例子可以扩展成 客户进程—服务器进程 通信的实例,write_fifo的作用类似于客户端,可以打开多个客户端向一个服务器发送请求信息,read_fifo类似于服务器,它适时监控着FIFO的读端,当有数据时,读出并进行处理,但是有一个关键的问题是,每一个客户端必须预先知道服务器提供的FIFO接口,下图显示了这种安排:
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
可以做到进程间的相互发信息,而前面的无名,有名双工管道FIFO只能做单向操作。
1、特点
操作思维示意:
A进程,B进程可以在linux内核中的链表消息队列中随意插入,读取数据
2、原型
#include
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
在以下两种情况下,msgget将创建一个新的消息队列:
函数msgrcv在读取消息队列时,type参数有下面几种情况:
可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。(其他的参数解释,请自行Google之)
3、例子
服务端程序一直在等待特定类型的消息,当收到该类型的消息以后,发送另一种特定类型的消息作为反馈,客户端读取该反馈并打印出来。
开始接收信息端:
gcc msgGet.c -o get
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
typedef struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
}Msg;
int main()
{
//获取
Msg readBuf;
int msgId = msgget(0x1234, IPC_CREAT|0777);
if(msgId == -1){
printf("get que failuer\n");
}
msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 888, 0);
printf("read from que:%s\n",readBuf.mtext);
Msg sendBuf = {988, "thank you for reach"};
msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
return 0;
}
开始发送信息端:
gcc msgSend.c -o send
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
typedef struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
}Msg;
int main()
{
//获取
Msg sendBuf = {888, "this is message from quen"};
int msgId = msgget(0x1234, IPC_CREAT|0777);
if(msgId == -1){
printf("get que failuer\n");
}
msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
Msg readBuf;
msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 988, 0);
printf("reaturn from get:%s\n",readBuf.mtext);
return 0;
}
这样就基本完成了进程间通信,而且这两个进程跟消息队列是相互隔离的
头文件:
#include
#include
函数原型:
key_t ftok( const char * fname, int id )
fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:
key_t key;
key = ftok(".", 1); 这样就是将fname设为当前目录。
id是子序号。虽然是int类型,但是只使用8bits(1-255)。
在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
如指定文件的索引节点号为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 );[/code]
同一段程序,用于保证两个不同用户下的两组相同程序获得互不干扰的IPC键值。
由于etc/config.ini(假定)为应用系统的关键配置文件,因此不存在被轻易删除的问题——即使被删,也会很快被发现并重建(此时应用系统也将被重启)。
ftok()的设计目的也在于此。
换索引减值key后的代码:
gcc msgGet.c -o get
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
typedef struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
}Msg;
int main()
{
//获取
Msg readBuf;
key_t key;
key = ftok(".",'z');
printf("key = %x\n",key);//以16进制打出来
int msgId = msgget(key, IPC_CREAT|0777);
if(msgId == -1){
printf("get que failuer\n");
}
msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 888, 0);
printf("read from que:%s\n",readBuf.mtext);
Msg sendBuf = {988, "thank you for reach"};
msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
return 0;
}
gcc msgSend.c -o send
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
typedef struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
}Msg;
int main()
{
//获取
Msg sendBuf = {888, "this is message from quen"};
key_t key;
key = ftok(".",'z');
printf("key = %x\n",key);//以16进制打出来
int msgId = msgget(key, IPC_CREAT|0777);
if(msgId == -1){
printf("get que failuer\n");
}
msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
Msg readBuf;
msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 988, 0);
printf("reaturn from get:%s\n",readBuf.mtext);
return 0;
}
加了关闭消息队列的代码:
gcc msgGet.c -o get
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
typedef struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
}Msg;
int main()
{
//获取
Msg readBuf;
key_t key;
key = ftok(".",'z');
printf("key = %x\n",key);//以16进制打出来
int msgId = msgget(key, IPC_CREAT|0777);
if(msgId == -1){
printf("get que failuer\n");
}
msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 888, 0);//读数据
printf("read from que:%s\n",readBuf.mtext);
Msg sendBuf = {988, "thank you for reach"};
msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);//发数据
msgctl(msgId, IPC_RMID, NULL);//控制关闭消息队列
return 0;
}
gcc msgSend.c -o send
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
typedef struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
}Msg;
int main()
{
//获取
Msg sendBuf = {888, "this is message from quen"};
key_t key;
key = ftok(".",'z');
printf("key = %x\n",key);//以16进制打出来
int msgId = msgget(key, IPC_CREAT|0777);
if(msgId == -1){
printf("get que failuer\n");
}
msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);//发数据
printf("send ovre!\n");
Msg readBuf;
msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 988, 0);//读数据
printf("reaturn from get:%s\n",readBuf.mtext);
msgctl(msgId, IPC_RMID, NULL);//控制关闭消息队列
return 0;
}
IPC_RMID 关闭内核中msgId的ID消息队列
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
1、特点
2、原型
#include
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr);
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。
当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。
shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。
共享内存使用案例:
写入共享内存
gcc shmw.c -o w
#include <sys/types.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
//int shmctl(int shmid, int cmd, struct shmid_ds *buf); //删除共享内存
//void *shmat(int shmid, const void *shmaddr, int shmflg); //把共享内存映射到进程
//int shmdt(const void *shmaddr); //释放掉共享内存,就把进程里连接共享内存的连接断开,但系统中共享内存还在
//int shmget(key_t key, size_t size, int shmflg); //删除系统中的共享内存
int main()
{
int shmid;
char *shmaddr;
key_t key;
key = ftok(".", 1);
shmid = shmget(key, 1024*4, IPC_CREAT|0666); //创建/打开共享内存
if(shmid == -1){
printf("shmget no ok!\n");
exit(-1);
}
shmaddr = shmat(shmid, 0, 0); //挂载共享内存
printf("shmat ok!\n");
strcpy(shmaddr, "yangyingchu"); //操作共享内存,写入共享内存数据
sleep(5); //等待时间,等待其它进程把数据读走
shmdt(shmaddr); //断开共享内存的连接
shmctl(shmid, IPC_RMID, 0); //删除共享内存
printf("quit\n");
return 0;
}
读取共享内存数据
gcc shmr.c -o r
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
//int shmctl(int shmid, int cmd, struct shmid_ds *buf); //删除共享内存
//void *shmat(int shmid, const void *shmaddr, int shmflg); //把共享内存映射到进程
//int shmdt(const void *shmaddr); //释放掉共享内存,就把进程里连接共享内存的连接断开,但系统中共享内存还在
//int shmget(key_t key, size_t size, int shmflg); //删除系统中的共享内存
int main()
{
int shmid;
char *shmaddr;
key_t key;
key = ftok(".", 1);
shmid = shmget(key, 1024*4, 0); //创建/打开共享内存
if(shmid == -1){
printf("shmget no ok!\n");
exit(-1);
}
shmaddr = shmat(shmid, 0, 0); //挂载共享内存
printf("shmat ok!\n");
printf("data: %s",shmaddr); //操作共享内存,读共享内存数据
shmdt(shmaddr); //断开共享内存的连接
printf("quit\n");
return 0;
}
ipcs -m
查看系统中的共享内存
ipcrm -m 32782
删除系统中共享内存ID为32782的共享内存
对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。
信号的处理:
信号的处理有三种方法,分别是:忽略、捕捉和默认动作
信号使用:
其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。
kill -9 ID
对于信号来说,最大的意义不是为了杀死进程,而是为了实现一些异步通讯的手段,即信号的三种使用方法中的捕捉信号
总结:
信号对linux系统来说是一个很重要的异步通信的方法
比如按 ctrl+c 能来中段程序,就是发送了一个信号
kill -l 来查看系统中的信号
信号一般的处理方式为:忽略、捕捉和默认动作
SIGKILL和SIGSTOP这两个信号不能被忽略
kill -9 ID、kill -SIGKILL ID来操控信号
入门版:函数signal
高级版:函数sigaction
信号处理发送函数:
1.入门版:kill
2.高级版:sigqueue
#include <stdio.h>
#include <signal.h>
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
printf("get signum= %d\n",signum);
printf("never quit\n");
}
int main()
{
signal(SIGINT, handler); // 当接收到 ctrl+c 信号后传给 handler 函数处理
while(1);
return 0;
}
此进程用 Ctrl + C 不能终止要用 kill -9 ID 来杀死进程
处理其它信号:
#include <stdio.h>
#include <signal.h>
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
printf("get signum= %d\n",signum);
switch(signum){
case 2:
printf("SIGINT\n"); //处理Ctrl+C指令
break;
case 9:
printf("SIGKILL\n"); //处理不了,系统会规避
break;
case 10:
printf("SIGUSR1\n");
break;
}
printf("never quit\n");
}
int main()
{
signal(SIGINT, handler); // 捕捉ctrl+c信号
signal(SIGKILL, handler); //捕捉kill指令信号
signal(SIGUSR1, handler); //发指令 kill -10 ID
while(1);
return 0;
}
用程序捕捉信号,执行信号动作
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
//int kill(pid_t pid, int sig);
int main(int argc, char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
kill(pid, signum);
printf("signum kill ok!\n");
return 0;
}
用system函数执行:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
//int kill(pid_t pid, int sig);
int main(int argc, char **argv)
{
int signum;
int pid;
char cmd[128] = {0};
signum = atoi(argv[1]);
pid = atoi(argv[2]);
sprintf(cmd, "kill -%d %d",signum, pid); //配置指令
system(cmd); //执行指令
printf("signum kill ok!\n");
return 0;
}
忽略信号:
#include <stdio.h>
#include <signal.h>
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
printf("get signum= %d\n",signum);
switch(signum){
case 2:
printf("SIGINT\n");
break;
case 9:
printf("SIGKILL\n");
break;
case 10:
printf("SIGUSR1\n");
break;
}
printf("never quit\n");
}
int main()
{
signal(SIGINT, SIG_IGN); // 捕捉 ctrl+c信号
signal(SIGKILL, handler);
signal(SIGUSR1, handler);
while(1);
return 0;
}
SIG_IGN 忽略信号
初级:
发信号:kill函数
控制:signal函数
重点放在了动作上
高级版:
发信号:sigqueue函数
控制:sigaction函数
sigqueue 的发送函数原型:
#include
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
sigaction 的处理函数原型:
#include
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
};
//回调函数句柄sa_handler、sa_sigaction只能任选其一
siginfo_t 结构体:
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}
si_value结构体:
#include
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
信号携带信息:
发整数
接收读端:
gcc new.c -o new
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
//int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//void (*sa_sigaction)(int, siginfo_t *, void *);
void handler(int signum, siginfo_t *info, void *context)
{
printf("get signum %d\n",signum);
if(context != NULL){
printf("get data = %d\n",info->si_int);
printf("get data = %d\n",info->si_value.sival_int);
printf("get from pid = %d\n",info->si_pid);
}
}
int main()
{
struct sigaction act;
printf("pid = %d\n",getpid());
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO; //be able to get message
sigaction(SIGUSR1, &act, NULL);
while(1);
return 0;
}
发信息端:
gcc send.c -o send
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
//int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc, char **argv)
{
int signum = atoi(argv[1]);
int pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;
sigqueue(pid, signum, value);
printf("pid = %d done!\n",getpid());
return 0;
}
发送字符串:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/wait.h>
void sig_action(int signo, siginfo_t *info, void *addr)
{
if (signo == SIGUSR1)
printf ("the signo is : SIGUSR1 \n");
printf("%s\n", (char*)info->si_value.sival_ptr); //打印随着信号一起传递过来的字符串
}
int main(int argc, char* const argv[])
{
struct sigaction act;
act.sa_sigaction = sig_action; //定义接受信号的处理函数
act.sa_flags = SA_SIGINFO; //设置该参数表示可以接收额外的参数;
sigaction(SIGUSR1, &act, NULL); //这个是信号的接受函数
pid_t pid;
if ((pid = fork()) == -1)
printf ("fault fork \n");
else if (pid == 0)
{
union sigval val;
val.sival_ptr = "hello world"; //把字符串发在 sigqueue函数的第三个参数中会在信号发送时整合到在sigqueue函数的>第二个参数中,一起发送给接受信号的进程
sigqueue(getppid(), SIGUSR1, val); //这个是信号的发送函数
}
while (((pid = wait(NULL)) == -1 && errno == EINTR) || pid > 0) {}
return 0;
}
发送字符串只能在共享内存或者同一进程下才可以发送
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
1、特点
2、原型
#include
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);
当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。
在semop函数中,sembuf结构的定义如下:
struct sembuf
{
short sem_num; // 信号量组中对应的序号,0~sem_nums-1
short sem_op; // 信号量值在一次操作中的改变量
short sem_flg; // IPC_NOWAIT, SEM_UNDO
}
其中 sem_op 是一次操作中的信号量的改变量:
若sem_op > 0,表示进程释放相应的资源数,将 sem_op 的值加到信号量的值上。如果有进程正在休眠等待此信号量,则换行它们。
若sem_op < 0,请求 sem_op 的绝对值的资源。
若sem_op == 0,进程阻塞直到信号量的相应值为0:
在semctl函数中的命令有多种,这里就说两个常用的:
控制信号量的基础框架:
#include <sys/ipc.h>
#include <sys/sem.h>
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
int main(int argc, char **argv)
{
key_t key = ftok(".",2);
int semid;
//key 信号量集合中有一个信号量
semid = semget(key, 1, IPC_CREAT|0666); //获取信号量,或者创建信号量
union semun initsem;
initsem.val = 1;
//0 代表操作第0个信号量
semctl(semid, 0, SETVAL, initsem); //初始化信号量
//SETVAL 设置信号量的值,设置为:initsem
int pid = fork();
if(pid > 0){
//去拿锁
printf("this is father!\n");
//锁放回去
}
else if(pid == 0){
printf("this is child\n");
}
else{
printf("fork error\n");
}
return 0;
}
信号量加锁后:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
//int semop(int semid, struct sembuf *sops, size_t nsops);
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void pGetKey(int semid)
{
struct sembuf sops;
sops.sem_num = 0; /* Operate on semaphore 0 */
sops.sem_op = -1; /* Wait for value to equal 0 */
sops.sem_flg = SEM_UNDO;
if (semop(semid, &sops, 1) == -1) {
perror("semop p");
exit(-1);
}
printf("getkey!\n");
}
void vPutBackKey(int semid)
{
struct sembuf sops;
sops.sem_num = 0; /* Operate on semaphore 0 */
sops.sem_op = 1; /* Wait for value to equal 0 */
sops.sem_flg = SEM_UNDO;
if (semop(semid, &sops, 1) == -1) {
perror("semop v");
exit(-1);
}
printf("put back the key!\n");
}
int main(int argc, char **argv)
{
key_t key = ftok(".",2);
int semid;
//key 信号量集合中有一个信号量
semid = semget(key, 1, IPC_CREAT|0666); //获取信号量,或者创建信号量
union semun initsem;
initsem.val = 0;
//0 代表操作第0个信号量
semctl(semid, 0, SETVAL, initsem); //初始化信号量
//SETVAL 设置信号量的值,设置为:initsem
int pid = fork();
if(pid > 0){
//去拿锁
pGetKey(semid);
printf("this is father!\n");
vPutBackKey(semid);
//锁放回去
semctl(semid, 0, IPC_RMID, initsem);
//销毁信号量集
}
else if(pid == 0){
//pGetKey(semid);
printf("this is child\n");
vPutBackKey(semid);
}
else{
printf("fork error\n");
}
return 0;
}
执行结果:
this is child
put back the key!
getkey!
this is father!
put back the key!
进程间通信参考链接 ↩︎
信号参考链接 ↩︎