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;
}

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

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,失败返回-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;
}

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,失败返回-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;
}

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)
信号量不能传递复杂消息,只能用来同步
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
进程间通信是指在不同进程进行数据交换和同步的一种技术。Linux提供了多种进程间通信方式,包括管道、消息队列、共享内存和信号量等。在实验六,我们将学习如何使用这些方式进行进程间通信。 1. 管道 管道是一种半双工的通信方式,它可以在两个进程传递数据。在Linux,管道分为匿名管道和命名管道。匿名管道只能用于父子进程的通信,而命名管道可以用于任意两个进程的通信。 使用匿名管道进行进程间通信的步骤如下: - 父进程创建管道,并调用fork函数创建子进程。 - 子进程通过管道接收数据。 - 父进程通过管道发送数据。 - 子进程接收到数据后进行处理。 使用命名管道进行进程间通信的步骤如下: - 创建命名管道。 - 打开命名管道并进行读写操作。 2. 消息队列 消息队列是一种进程间通信机制,它允许不同进程通过一个消息传递序列来进行通信。在Linux,每个消息都有一个类型,接收进程可以选择接收某个特定类型的消息。 使用消息队列进行进程间通信的步骤如下: - 创建消息队列。 - 发送消息到消息队列。 - 接收消息并进行处理。 3. 共享内存 共享内存是一种进程间通信方式,它允许不同进程共享同一个物理内存区域。这种方式比较高效,但需要考虑进程的同步和互斥问题,否则会出现数据不一致的情况。 使用共享内存进行进程间通信的步骤如下: - 创建共享内存区域。 - 进程通过共享内存区域进行数据交换。 - 进程需要进行同步和互斥操作。 4. 信号量 信号量是一种进程同步的机制,它可以用来保证不同进程的共享资源在同一时刻只能被一个进程访问。在Linux,每个信号量都有一个计数器,当计数器为0时,进程需要等待;当计数器大于0时,进程可以继续执行。 使用信号量进行进程间通信的步骤如下: - 创建信号量。 - 进程对信号量进行P操作(等待)。 - 进程对信号量进行V操作(释放)。 总体来说,不同的进程间通信方式各有优缺点,应根据实际需求选择适合的方式。在实验六,我们将通过编写代码来学习如何使用这些方式进行进程间通信

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值