您现在的位置是:首页 > 正文

Linux系统编程之进程间通信方式介绍 最新发布

2024-04-01 02:48:35阅读 3

进程间常见通信方式介绍

本章讲述单机版的通信方式(IPC,InterProcess Communication),依赖进程间构建各类消息通道,对于这类消息通道的操作流程通常是:创建 / 打开 --> 发送 --> 接收 --> 关闭
常见的通信方式包括:

通信方式 API
无名管道 pipe,read,write,close
命名管道(FIFO) mkfifo,read,write,close
消息队列 magget,msgsnd,msgrcv,msgctl,ftok
共享内存 shmget,shmat,shmdt,shmctl,ftok
信号 signal,kill,sigaction,sigqueue
信号量 semget,semop,semctl

一、无名管道

无名管道作为最基础的通信方式,通常只作用于父子进程,言外之意不同程序文件上的无名管道无法进行数据沟通;而且这种方式是半双工形式,数据只能在一个方向流动,只能开放一个读端和写端。可以讲管道看成是一种特殊文件,只存在内存中,采用read、write等函数进行操作。

下面提供一段示例代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main() {
        int fd[2];
//int pipe(int pipefd[2]);
        pid_t pid;


	int pnum = pipe(fd);
	if(pnum==-1){
		printf("create pipe failed.\n");
                exit(-1);
	}
	pid = vfork();
	if(pid < 0)
		printf("create child pid error.\n");
	else if(pid ==0){
               close(fd[0]);
	       char writebuf[128] = "Hello from child pid.";
	       write(fd[1],writebuf,strlen(writebuf));
	       exit(0);
	}
        else{
               close(fd[1]);
               char readbuf[128];
               read(fd[0],readbuf,128);
	       printf("Message read from child pid: %s\n",readbuf);
        }

	return 0;
}

子进程对管道进行写入,父进程进行读取,可以看出进程每次需要写入或读取数据需要关闭自身对应的读端或写端,并不方便。

lamda@lamda-virtual-machine:~/Desktop/code/Linux/IPC$ gcc demo_pipe.c -o pipe
lamda@lamda-virtual-machine:~/Desktop/code/Linux/IPC$ ./pipe
Message read from child pid: Hello from child pid.

二、命名管道

命名管道比起无名管道而言,改进了不同程序文件通信的弊端,但仍然属于半双工形式,需要关闭读端或写端。

demo_fifo_send.c

#include <stdio.h>
#include <errno.h>
#include <error.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
//demo_fifo_send.c
//int mkfifo(const char *pathname, mode_t mode);
    int fifo_num = mkfifo("FILE",0600);
	if((fifo_num==-1)&&(errno != EEXIST)){
		printf("create fifo failed.\n");
		perror("why");
		exit(-1);
	}
     int fd = open("FILE",O_RDWR);
     char *str = "Write something from demo_fifo_send.c";
     int nwrite = write(fd,str,strlen(str));
     close(fd);
     return 0;
}

demo_fifo_read.c

#include <stdio.h>
#include <errno.h>
#include <error.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
//demo_fifo_send.c
//int mkfifo(const char *pathname, mode_t mode);
    int fd = open("FILE",O_RDWR);
    char readbuf[128] = {0};
	int nread = read(fd,readbuf,128);
	printf("read from fifo_send %d bytes, %s\n",nread,readbuf);
    close(fd);
	return 0;
}

三、消息队列

消息队列是以链表的方式存在于内核之中,通过给定消息队列ID和消息类型完成读取和写入数据,相关函数原型如下:

函数作用 函数原型
创建或打开消息队列:成功返回队列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)

提供一段示例代码,完成读取和写入操作。

demo_msgque_send.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.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);
//key_t ftok(const char *pathname, int proj_id);
struct msgbuf {
               long mtype ;       /* message type, must be > 0 */
               char mtext[128];     /* message data */
};

int main()
{
        int proj_id = 26;
//printf("choose one int num to create msgid,such as:27 35.\n");
//scanf("%d",&proj_id);
        key_t key = ftok(".",proj_id);
        int msgid = msgget(key,IPC_CREAT|0777);
        if(msgid==-1){
                printf("create msgque error.\n");
                exit(-1);
        }
        struct msgbuf sendbuf = {988,"send something from msgque_send."};

//sendbuf.mtext = "send something from msgque_send.";
        msgsnd(msgid,&sendbuf,128,0);
//msgrcv(msgid,&readbuf,128,988,0);
        printf("done\n");
        struct msgbuf readbuf;
        int i = msgrcv(msgid,&readbuf,128,988,0);
        printf("recieve from msgque %d bytes:%s\n",i,readbuf.mtext);
        msgctl(msgid,IPC_RMID,NULL);

        return 0;
}

demo_msgque_read.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.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);
//key_t ftok(const char *pathname, int proj_id);
//int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf {
               long mtype;         /* message type, must be > 0 */
               char mtext[128];    /* message data */
};

int main()
{
        int proj_id = 26;
//printf("choose one int num to create msgid,such as:27 35.\n");
//scanf("%d",&proj_id);
        key_t key = ftok(".",proj_id);
        int msgid = msgget(key,IPC_CREAT|0777);
        if(msgid==-1){
                printf("create msgque error.\n");
                exit(-1);
        }
        struct msgbuf readbuf;
        int i = msgrcv(msgid,&readbuf,128,988,0);
        printf("recieve from msgque %d bytes:%s\n",i,readbuf.mtext);

        struct msgbuf sendbuf = {988,"I have seen.\n"};
        int j = msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
        printf("done.\n");
        msgctl(msgid,IPC_RMID,NULL);
        return 0;
}

四、共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

函数作用 函数原型
创建或获取一个共享内存:成功返回共享内存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)

demo_shm_send.c

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

int main() {
//int shmget(key_t key, size_t size, int shmflg);
        key_t key = ftok(".",1);
        int shmid = shmget(key,1024*4,IPC_CREAT|0666);
        if(shmid == -1){
                printf("create share_memory failed.\n");
                exit(-1);
        }
        char *shmaddr;
        printf("shmid = %d\n",shmid);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
        shmaddr = shmat(shmid,0,0);
        printf("shmat ok.\n");
        strcpy(shmaddr,"share_memory send something.");
//int shmdt(const void *shmaddr);
        sleep(5);
        shmdt(shmaddr);
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
        shmctl(shmid,IPC_RMID,0);
        return 0;
}

demo_shm_read.c

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

int main() {
//int shmget(key_t key, size_t size, int shmflg);
        key_t key = ftok(".",1);
        int shmid = shmget(key,1024*4,IPC_CREAT|0666);
        if(shmid == -1){
                printf("create share_memory failed.\n");
                exit(-1);
        }
        char *shmaddr;
        printf("shmid = %d\n",shmid);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
        shmaddr = shmat(shmid,0,0);
//printf("shmat ok.\n");

//strcpy(shmaddr,"share_memory send something.");
//int shmdt(const void *shmaddr);
//sleep(5);
        printf("read from share_memory : %s\n",shmaddr);
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
        shmctl(shmid,IPC_RMID,0);
        return 0;
}

五、信号

Linux信号(signal)

信号处理方式主要有:忽略、捕捉和默认动作。每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。信号定义在signal.h头文件中,信号名都定义为正整数。具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。

lamda@lamda-virtual-machine:~/Desktop/code/Linux/IPC$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

提供一段示例代码,完成对信号的捕捉,但SIGKILL和SIGSTOP不能忽略。

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


void handler(int signum){
        printf("signum = %d\n",signum);
}
//sighandler_t signal(int signum, sighandler_t handler);
//typedef void (*sighandler_t)(int);

int main(){
//int signum;
        printf("pid = %d",getpid());
        signal(SIGUSR1,handler);
        signal(SIGINT,SIG_IGN);
        while(1);
        return 0;
}

六、信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。一般用于进程间同步,若要在进程间传递数据需要结合共享内存。

#include <stdio.h>
#include <string.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, ...);
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 semop(int semid, struct sembuf *sops, size_t nsops);

void set_get(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;
           semop(semid,&sops,1);
	   printf("get key.\n");

}
void set_put(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;
           semop(semid,&sops,1);
	   printf("put key.\n");
}
int main(){
	int semid;
	key_t key;
	key = ftok(".",1);
	semid = semget(key,1,IPC_CREAT|0666);
	union semun set_sem;
	set_sem.val = 0;
	semctl(semid,0,SETVAL,set_sem);
	int pid = fork();	
	if(pid >0){
		set_get(semid);
		printf("this is father pid.\n");
		set_put(semid);
	}else if(pid ==0){
		printf("this is child pid.\n");
		set_put(semid);
	}else{
		printf("fork error.\n");
	}
        semctl(semid, 0, IPC_RMID, set_sem);

	return 0;
}

七、总结

通信方式 效果
无名管道 速度慢,容量有限,只有父子进程能通讯
命名管道 (FIFO) 任何进程间都能通讯,但速度慢
消息队列 容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
共享内存 能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
信号 初级版信号函数(signal和kill)只用来收发指令,无法传递数据,需要用到高级版信号函数(sigaction和sigqueue)
信号量 不能传递复杂消息,只能用来同步

网站文章

  • spring加载流程refresh之finishBeanFactoryInitialization(beanFactory)

    spring加载流程refresh之finishBeanFactoryInitialization(beanFactory)

    实例化所有BeanAbstractApplicationContext.finishBeanFactoryInitialization()判断是否有bdName为conversionService的b...

    2024-04-01 02:48:27
  • nginx的配置、虚拟主机、负载均衡和反向代理--03

    第3篇主要讲 URL路由重写 和 读写分离nginx中的 location 正则模块nginx中的URL重写怎么做。url重写模块,主要是在location模块面来实现,我们一点一点的看。首先看下location 正则匹配的使用。还记得之前是如何用location来定位.php文件的吗?location ~ \.php$ { fastcgi_pass 127.0.0.1:9000

    2024-04-01 02:48:18
  • SQL_数据库基础之级联删除和级联更新

    SQL_数据库基础之级联删除和级联更新

    原文: 级联删除 删除包含主键值的行的操作,该值由其它表的现有行中的外键列引用。在级联删除中,还删除其外键值引用删除的主键值的所有行。 语法: FOREIGN KEY(COLUMN[,...n]) REFERENCES referenced_table_name[(ref_column[,...n])] [ON DELETE cascade] [ON UPDATE cascad

    2024-04-01 02:48:11
  • 博客-地图

    本页面用来博客文章整理。在未来一段时间的计划中,本文将是唯一的置顶文章,以方便查看博客的所有内容。本博客所有内容都是与视觉SLAM相关的内容,希望本博客可以对读者您有所帮助,如有改进意见,欢迎评论区留言批评指正!由于本人的个人博客网站暂时不维护了,所以将博客内的一些文章重新搬过来。

    2024-04-01 02:47:27
  • go语言中slice的容量和内存地址

    slice是引用这个我就不说了,不会有人不知道吧?对于slice的赋值语句,如果没有指定容量,那么容量会与赋值等式的右值一致。var arr = []int{0,1,2}nrr := arr[:1] ...

    2024-04-01 02:47:20
  • Linux内存管理 - PAGE_OFFSET理解

    Linux内存管理 - PAGE_OFFSET理解     PAGE_OFFSET 代表的是内核空间和用户空间对虚拟地址空间的划分,对不同的体系结构不同。比如在32位系统中3G-4G 属于内核使用的内存空间,所以 PAGE_OFFSET = 0xC0000000。在X86-64架构下是ffff880000000000。可以看到内核程序可以可以访问从PAGE_OFFSET 之

    2024-04-01 02:47:13
  • 达梦8全量备份和增量备份备份策略

    达梦8全量备份和增量备份备份策略

    使用SYSDBA登录数据库manager工具,输入以下SQL语句,并执行。

    2024-04-01 02:46:32
  • 软件测试员200题(练习)

    所谓的“录制-回放”,就是先使用自动化工具由手工完成一遍需要测试的流程,同时由计算机记录下这个流程期间操作,并形成特定的( )。① 测试用例 ② 测试人员的分配 ③ 测试的度量标准 ④ 测试工具的选用...

    2024-04-01 02:46:24
  • 项目启动报java.lang.IllegalAccessError: tried to access method 热门推荐

    项目启动报java.lang.IllegalAccessError: tried to access method 热门推荐

    最近在对接京东代扣相关的业务,拿到京东的Demo,可以很快地把整套的流程跑下来,但当把Demo集成到项目中时,只要启动项目就不停地报错,错误信息如下:2017-10-13 19:34:18 ERROR _500_jsp _jspService - Handler processing failed; nested exception is java.lang.IllegalAccessError:

    2024-04-01 02:46:16
  • Tor

    Tor

    先打开这个网站 https://bridges.torproject.org/ Step #1:下载Tor浏览器 Step #2:获取bridges Step #3:在Tor浏览器上添加网桥 Step #4:调出项目结构管理区域 把网桥全部复制进去 在点击连接 注:连接第一次的时候比较慢 -End- 暗网搜索引擎 http://www.onion.l

    2024-04-01 02:45:37