进程间通信概述

进程间通信(InterProcess Communication,IPC)是指在不同进程之间传播或交换信息。

Linux的进程间通信方法有管道(Pipe)和有名管道(FIFO)、信号(Signal)、消息队列(Message Queue)、共享内存(Shared Memory)、信号量(Semaphore)、套接字(Socket)等。

Linux进程间通信由以下几部分发展而来:

1、UNIX进程间通信

2、基于System V 进程间通信

3、POSIX进程间通信

POSIX进程间通信是最新的技术,POSIX表示可移植操作系统接口。

管道和有名管道

管道是半双工的(即数据单向流动,先进先出),有固定的读端和写端。数据被一个进程读出后,将被从管道删除,其他进程将不能再读到这些数据。

管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞。

管道包括无名管道(Pipe)和有名管道(FIFO)两种。无名管道用于父进程和子进程间的通信,有名管道可用于运行于同一系统中的任意两个进程间的通信。

无名管道

函数用法

#include<unistd.h>

int pipe(int fd[2]);

函数功能

创建无名管道

fd:文件描述符,fd[0]用于读管道,fd[1]用于写管道

函数返回值 成功返回 0 , 失败返回 -1 并产生errno

当一个无名管道(Pipe)建立时,它会创建两个文件描述符:fd[0]用于读管道fd[1]用于写管道。

要关闭管道只需要关闭这两个文件描述符即可。

管道用于不同进程间通信。通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。必须在系统调用fork( )前调用pipe( ),否则子进程将不会继承文件描述符。

实例:使用管道实现数据的发送与接收

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>void ReadPipe(int fd)//读管道
{int ret;char buf[32] = {0};while(1){ret = read(fd, buf, sizeof(buf));if (-1 == ret){perror("read");exit(1);}if (!strcmp(buf, "bye")){break;}printf("read from pipe: %s\n", buf);memset(buf, 0, sizeof(buf));}close(fd);
}void WritePipe(int fd)//写管道
{int ret;char buf[32] = {0};while (1){scanf("%s", buf);ret = write(fd, buf, strlen(buf));if (-1 == ret){perror("write");exit(1);}if (!strcmp(buf, "bye")){break;}memset(buf, 0, sizeof(buf));}close(fd);
}int main()
{int fd[2];int ret;pid_t pid;ret = pipe(fd);   //创建管道if (-1 == ret){perror("pipe");exit(1);}pid = fork();  //创建进程if (-1 == pid){perror("fork");exit(1);}else if (0 == pid){close(fd[1]);        //关闭写端口ReadPipe(fd[0]); //fd[0]读数据}else{close(fd[0]);       //关闭读端口WritePipe(fd[1]);    //fd[1]写数据int status;wait(&status);}return 0;
}

有名管道

函数用法

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

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

函数功能

创建有名管道

pathname:文件名

mode:设定创建的文件的权限

函数返回值 成功返回 0 , 失败返回 -1 并产生errno

命名管道(FIFO)与无名管道的区别在于它提供一个路径与之关联,以FIFO的文件形式存储于文件系统中。命名管道是一个设备文件。因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。实例:使用有名管道,实现一个终端发送数据,一个终端接收数据

fifo_read.c

#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{char buf[32] = {0};int fd;int ret;fd= mkfifo("fifo.tmp", S_IRWXU);//创建有名管道if (-1 == fd){perror("mkfifo");exit(1);}fd = open("fifo.tmp", O_RDONLY);//只读方式打开文件fifo.tmpif (-1 == fd){perror("open");exit(1);}while(1){ret = read(fd, buf, sizeof(buf));//从文件中读取数据if (-1 == ret){perror("read");exit(1);}if (!strcmp(buf, "bye"))//读取到bye结束循环{break;}printf("read:%s\n", buf);memset(buf, 0, sizeof(buf));}close(fd);unlink("fifo.tmp");//在管道使用结束后删除文件fifo.tmpreturn 0;
}

 fifo_write.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd;int ret;char buf[32] = {0};fd = open("fifo.tmp", O_WRONLY);//以只写方式打开文件fifo.tmpwhile (1){scanf("%s", buf);ret = write(fd, buf, strlen(buf));//写入数据到文件if (-1 == ret){perror("write");exit(1);}if (!strcmp(buf, "bye"))//输入bye结束写入{break;}memset(buf, 0, sizeof(buf));}close(fd);return 0;
}

注:在运行测试时候必须先运行创建了管道的程序,本文中要先运行fifo_read.c

信号

信号(signal)机制是Unix系统中最为古老的进程间通信机制,很多条件可以产生一个信号:

(1)当用户按某些按键时,产生信号。

(2)硬件异常产生信号:除数为0、无效的存储访问等等。这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程,例如,内核对正访问一个无效存储区的进程产生一个SIGSEGV信号 。

(3)进程用kill函数将信号发送给另一个进程。

(4)用户可用kill命令将信号发送给其他进程。

当某信号出现时,将按照下列三种方式中的一种进行处理:

(1)忽略此信号,大多数信号都按照这种方式进行处理,但有两种信号决不能被忽略,它们是: SIGKILL\SIGSTOP。 这是因为这两种信号向超级用户提供了一种终止或停止进程的方法。

(2)执行系统默认动作,对大多数信号的系统默认动作是终止该进程。

(3)执行用户希望的动作,通知内核在某种信号发生时,调用一个用户函数。在用户函数中,执行用户希望的处理。

在终端中输入 kill -l 可以查看到系统设定的信号的宏定义。

发送信号的函数有:kill(向任意进程发送信号)、raise(只能向当前进程发送信号)、abort(发送SIGABRT信号,可以让进程异常终止)、alarm(发送SIGALRM闹钟信号)

实例:实现0-59的计时,计时到60时变为0.

使用alarm函数可以设置一个时间值(闹钟时间),当所设置的时间到了时,产生SIGALRM信号。然后使用signal函数捕捉SIGALRM信号,并且自定义信号处理方式。

#include<stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
int t;void print()
{system("clear");//清屏alarm(1);//1秒后给当前进程发送一个SIGALRM信号,一直在执行printf("%d\n", (t++)%60);}int main()
{system("clear");//清屏alarm(1);//1秒后给当前进程发送一个SIGALRM信号,只执行一次signal(SIGALRM, print);while (1);//必须有,不然进程就直接结束了return 0;
}

消息队列

消息队列(Message Queue),就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。

消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。

上图中进程A指定了类型1,进程C指定了类型2,,进程B指定了类型1。按上图演示,最后进程B会接收到进程A发送的hello,进程C的world接收不到,因为B和C不在同一类型。

实例:使用消息队列和fork,实现两个终端的收发数据,既能收,又能发。

msg_send.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>#define MSGKEY 1234struct msgbuf
{long mtype;     /* message type, must be > 0 */char mtext[100];  /* message data */
};int main()
{int msgid;int ret;struct msgbuf mbuf;pid_t pid;msgid = msgget(MSGKEY, IPC_CREAT | IPC_EXCL);  //创建消息队列if (-1 == msgid){perror("msgget");exit(1);}pid = fork();if (-1 == pid){perror("fork");exit(1);}else if (0 == pid)        //子进程发送数据{while (1){memset(mbuf.mtext, 0, sizeof(mbuf.mtext));scanf("%s", mbuf.mtext);mbuf.mtype = 1;ret = msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);if (-1 == ret){perror("msgsnd");exit(1);}if (!strcmp(mbuf.mtext, "bye")){mbuf.mtype = 2;msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);break;}}}else     //父进程接收数据{while (1){memset(mbuf.mtext, 0, sizeof(mbuf.mtext));ret = msgrcv(msgid, &mbuf, sizeof(mbuf.mtext), 2,0);if (-1 == ret){perror("msgrcv");exit(1);}if (!strcmp(mbuf.mtext, "bye")){kill(pid, 2);break;}printf("\t%s\n", mbuf.mtext);}}sleep(1);msgctl(msgid, IPC_RMID, NULL);//销毁消息队列return 0;
}
 msg_write.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>#define MSGKEY 1234struct msgbuf
{long mtype;     /* message type, must be > 0 */char mtext[100];  /* message data */
};int main()
{int msgid;int ret;struct msgbuf mbuf;pid_t pid;msgid = msgget(MSGKEY, 0); //打开消息队列if (-1 == msgid){perror("msgget");exit(1);}pid = fork();if (-1 == pid){perror("fork");exit(1);}else if (0 == pid)        //子进程发送数据{while (1){memset(mbuf.mtext, 0, sizeof(mbuf.mtext));scanf("%s", mbuf.mtext);mbuf.mtype = 2;ret = msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);if (-1 == ret){perror("msgsnd");exit(1);}if (!strcmp(mbuf.mtext, "bye")){mbuf.mtype = 1;msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);break;}}}else     //父进程接收数据{while (1){memset(mbuf.mtext, 0, sizeof(mbuf.mtext));ret = msgrcv(msgid, &mbuf, sizeof(mbuf.mtext), 1,0);if (-1 == ret){perror("msgrcv");exit(1);}if (!strcmp(mbuf.mtext, "bye")){kill(pid, 2);break;}printf("\t%s\n", mbuf.mtext);}}return 0;
}

 注:测试程序时先启动创建了消息队列的程序,本文中应该是先启动msg_send.c

共享内存

共享内存允许一个或多个进程共享一个给定的物理存储区,这一个给定的物理存储区可以被两个或两个以上的进程映射至自身的地址空间中。

采用共享内存进行通信的一个主要好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝,对于像管道和消息队里等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次:一次从输入文件到共享内存区,另一次从共享内存到输出文件。

使用共享内存的步骤:

(1)申请共享内存   shmget

(2)映射                  shmat

(3)使用

(4)解除映射           shmdt

(5)销毁共享内存    shmctl

实例:

shm.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>#define SHMKEY 1234
#define SHMSIZE 4096int main()
{int shmid;void *shmaddr;int count = 0;shmid = shmget(SHMKEY, SHMSIZE, 0666|IPC_CREAT);//创建共享内存if (-1 == shmid){perror("shmget");exit(1);}shmaddr = shmat(shmid, NULL, 0);//映射到虚拟地址空间if (-1 == shmaddr){perror("shmat");exit(1);}*(int *)shmaddr = count;//把数据写到内存while(1){count = *(int *)shmaddr;//从内存读数据//usleep(100000);if (count > 100){break;}printf("A:%d\n", count);count++;*(int *)shmaddr = count;//数据写回内存usleep(100000);}shmdt(shmid);      //解除映射shmctl(shmid, IPC_RMID, NULL);//销毁映射return 0;
}

 shm2.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>#define SHMKEY 1234
#define SHMSIZE 4096int main()
{int shmid;void *shmaddr;int count = 0;shmid = shmget(SHMKEY, SHMSIZE, 0);//获取共享内存if (-1 == shmid){perror("shmget");exit(1);}shmaddr = shmat(shmid, NULL, 0);//映射到虚拟地址空间if (-1 == shmaddr){perror("shmat");exit(1);}while(1){count = *(int *)shmaddr;//从内存读数据//usleep(100000);if (count > 100){break;}printf("B:%d\n", count);count++;*(int *)shmaddr = count;//把数据写回共享内存usleep(100000);}return 0;
}

注:在测试时,应该先启动创建了共享内存的那个程序,也就是先启动shm.c。

测试现象是只启动了shm.c时数字刷新步长为1 (也就是 1 2 3 4 5 这样变),在shm.c运行时启动了shm2.c后,数字刷新步长为2 (也就是 1 3 5 7 这样变)

思考:如果将例子里的 延时usleep(100000);  挪到从内存读数据后面(也就是注释掉的那串代码)会发生什么现象?过程是怎么样子的?怎么解决?(解决方案见信号量实例)

信号量

信号量(Semaphore),又名信号灯,主要用途是保护临界资源。进程可以根据它来判定是否能够访问某些共享资源。除了用于访问控制外,还可用于进程同步。

二值信号灯:信号灯的值只能取0或1,类似于互斥锁。 但两者有不同:

信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;

互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。

计数信号灯:信号灯的值可以取任意非负值。

使用信号量的步骤

(1)创建(获取) 信号量         semget

(2)初始化信号量                semctl

(3)使用(P操作/V操作)        semop(4)销毁信号量                    semctl

实例:共享内存的例子的思考的解决方案。

sem.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>#define SHMKEY 1234
#define SHMSIZE 4096
#define SEMKEY 1234union 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 sem_p(int semid)
{int ret;struct sembuf sbuf;sbuf.sem_num = 0;//第一个sbuf.sem_op = -1;//P操作sbuf.sem_flg = SEM_UNDO;//SEM_UNDO进程异常自动UNDOret = semop(semid, &sbuf, 1);if (ret == -1){perror("semop");return;}
}
void sem_v(int semid)
{int ret;struct sembuf sbuf;sbuf.sem_num = 0;//第一个sbuf.sem_op = 1;//V操作sbuf.sem_flg = SEM_UNDO;//SEM_UNDO进程异常自动UNDOret = semop(semid, &sbuf, 1);if (ret == -1){perror("semop");return;}
}int main()
{int shmid;void *shmaddr;int count = 0;int semid;union semun unsem;semid = semget(SEMKEY, 1, IPC_CREAT | IPC_EXCL);   //创建信号量if (-1 == semid){perror("semget");exit(1);}unsem.val = 1;//初始化成二值信号量semctl(semid, 0, SETVAL, unsem);//初始化信号量shmid = shmget(SHMKEY, SHMSIZE, IPC_CREAT | IPC_EXCL);//创建共享内存if (-1 == shmid){perror("shmget");exit(1);}shmaddr = shmat(shmid, NULL, 0);//映射到虚拟地址空间if (NULL == shmaddr){perror("shmat");exit(1);}*(int *)shmaddr = count;//把数据写到内存while(1){sem_p(semid);//P操作count = *(int *)shmaddr;//从内存读数据usleep(100000);if (count > 100){break;}printf("A:%d\n", count);count++;*(int *)shmaddr = count;//数据写回内存//usleep(100000);sem_v(semid);//V操作}shmdt(shmaddr);        //解除映射sleep(1);shmctl(shmid, IPC_RMID, NULL);//销毁映射semctl(semid, 0, IPC_RMID);//销毁信号量return 0;
}

sem2.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>#define SHMKEY 1234
#define SHMSIZE 4096
#define SEMKEY 1234union 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 sem_p(int semid)
{int ret;struct sembuf sbuf;sbuf.sem_num = 0;//第一个sbuf.sem_op = -1;//P操作sbuf.sem_flg = SEM_UNDO;//SEM_UNDO进程异常自动UNDOret = semop(semid, &sbuf, 1);if (ret == -1){perror("semop_p");return;}
}
void sem_v(int semid)
{int ret;struct sembuf sbuf;sbuf.sem_num = 0;//第一个sbuf.sem_op = 1;//V操作sbuf.sem_flg = SEM_UNDO;//SEM_UNDO进程异常自动UNDOret = semop(semid, &sbuf, 1);if (ret == -1){perror("semop_v");return;}
}int main()
{int shmid;void *shmaddr;int count = 0;int semid;union semun unsem;semid = semget(SEMKEY, 1, 0);  //获取信号量if (-1 == semid){perror("semget");exit(1);}//unsem.val = 1;//semctl(semid, 0, SETVAL, unsem);//初始化信号量shmid = shmget(SHMKEY, SHMSIZE, 0);//获取共享内存if (-1 == shmid){perror("shmget");exit(1);}shmaddr = shmat(shmid, NULL, 0);//映射到虚拟地址空间if (NULL == shmaddr){perror("shmat");exit(1);}while(1){sem_p(semid);//P操作count = *(int *)shmaddr;//从内存读数据usleep(100000);if (count > 100){break;}printf("B:%d\n", count);count++;*(int *)shmaddr = count;//数据写回内存//usleep(100000);sem_v(semid);//V操作}shmdt(shmaddr);        //解除映射return 0;
}

linux c 进程间通信相关推荐

  1. Linux的进程间通信-消息队列

    Linux的进程间通信-消息队列 微博ID:orroz 微信公众号:Linux系统技术 前言 Linux系统给我们提供了一种可以发送格式化数据流的通信手段,这就是消息队列.使用消息队列无疑在某些场景的 ...

  2. Linux环境进程间通信(二): 信号--转载

    http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html http://www.ibm.com/developerworks ...

  3. Linux下进程间通信的六种机制详解

    linux下进程间通信的几种主要手段:        1.管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具 ...

  4. Linux下进程间通信概述

    1. Linux下进程间通信概述 P83-P84 将第一页和第二页合并起来讲了 引言:前面我们学习了一下进程,我们知道多,进程间的地址空间相对独立.进程与进程间不能像线程间通过全局变量通信. 如果想进 ...

  5. Linux环境进程间通信 信号量

    信号量与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制.相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志.除了用于访问控制外,还可用于进程 ...

  6. Linux进程+进程间通信IPC

    一 Linux进程 1) 进程的内存映像 2)解释 BSS段:在采用段式内存管理的架构中,BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域.BSS是英文Bloc ...

  7. 通信开源linux,Linux环境进程间通信

    Linux环境进程间通信(一) http://www.ibm.com/developerworks/cn/linux/l-ipc/part1/index.html Linux环境进程间通信(二): 信 ...

  8. Linux环境进程间通信(五): 共享内存(上)

    Linux环境进程间通信(五): 共享内存(上) 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B共享内存的意思是,同一块物理内存被映射到进程A.B各自的进程地址空间. ...

  9. linux实现单机qq_Linux后台服务器开发——Linux下进程间通信的方式有哪些?

    Linux下进程间通信的方式有: 管道 消息队列 信号 信号量 共享存储 套接字 一.管道 管道是半双工的,数据只能向一个方向流动:需要双方通信时,需要建立起两个管道 PIPE无名管道 详情参阅:ht ...

最新文章

  1. getname java_关于java:只获取类Class.getName()的名称
  2. 我能不能理解成 ssh中service就相当于与jsp+servlet+dao中的servlet???
  3. drupal与html转换,老葛 怎么改变drupal form 生成的html啊 ? | Think in Drupal
  4. 2019-04(2)Python学习
  5. Python中get()函数用法【详解】——Python系列学习笔记
  6. 你离云计算还差一个云管平台(CMP)
  7. 扑克游戏的洗牌算法及简单测试
  8. JDK 19 / Java 19 新特性
  9. Redis下载和安装
  10. MEME:motif分析的综合性工具
  11. 输入阻抗、输出阻抗与阻抗匹配
  12. html获取当前ip地址_IP地址精准查询
  13. ZYNQ基本使用(2) GPIO的使用
  14. UE《空山新雨后》总结笔记
  15. inner join 和outer join的区别
  16. 如何计算系统用户并发数,系统最大并发数
  17. 清华张钹院士专刊文章:迈向第三代人工智能(全文收录)
  18. (翻译)所见即所得(WYSIWYG)
  19. java实现环形链表解决约瑟夫环问题
  20. 将Openwrt路由器配置为无线交换机,实现同网段无线中继功能

热门文章

  1. 禁止用户对系统数据库表的SELECT权限
  2. 信号编程之sigaction函数和sigqueue函数
  3. LuManager 高危SQL注入0day分析
  4. selenium操作浏览器cookie方法
  5. Qt Designer信号和槽
  6. 2003:个人知识管理实施
  7. 用VS.NET中的测试工具测试ASP.NET程序
  8. PHP大法——实验吧
  9. 算法学习之路|完美数列
  10. LFS、BLFS、ALFS、HLFS的区别