当前位置:   article > 正文

Linux系统编程之进程间通信方式介绍_简述linux中进程间各种通信方式特点

简述linux中进程间各种通信方式特点

进程间常见通信方式介绍

本章讲述单机版的通信方式(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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

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

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.
  • 1
  • 2
  • 3

二、命名管道

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

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

三、消息队列

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

函数作用函数原型
创建或打开消息队列:成功返回队列ID,失败返回-1int msgget(key_t key, int flag)
添加消息队列:成功返回0,失败返回-1int msgsnd(int msqid, const void *ptr, size_t size, int flag)
读取消息队列:成功返回消息数据的长度,失败返回-1int msgrcv(int msqid, void *ptr, size_t size, long type,int flag)
控制消息队列:成功返回0,失败返回-1int 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

四、共享内存

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

函数作用函数原型
创建或获取一个共享内存:成功返回共享内存ID,失败返回-1int shmget(key_t key, size_t size, int flag)
连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1void *shmat(int shm_id, const void *addr, int flag)
断开与共享内存的连接:成功返回0,失败返回-1int shmdt(void *addr)
控制共享内存的相关信息:成功返回0,失败返回-1int 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

五、信号

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	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

提供一段示例代码,完成对信号的捕捉,但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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

六、信号量

信号量(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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

七、总结

通信方式效果
无名管道速度慢,容量有限,只有父子进程能通讯
命名管道 (FIFO)任何进程间都能通讯,但速度慢
消息队列容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
共享内存能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
信号初级版信号函数(signal和kill)只用来收发指令,无法传递数据,需要用到高级版信号函数(sigaction和sigqueue)
信号量不能传递复杂消息,只能用来同步
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/273122
推荐阅读
相关标签
  

闽ICP备14008679号