Linux系统编程——进程间通信(IPC)

一. 概述

1.1单机通信

  1. 半双工管道 (无名管道)
  2. 全双工管道FIFO(命名全双工管道)
  3. 消息队列
  4. 信号量
  5. 共享存储

1.2多机通信:

  1. 套接字
  2. STREAMS

二.管道通信

  • 管道,通常指无名管道,最古老的IPC形式

2.1特点

  1. 半双工(数据只能在同一方向流动),有固定的读端和写端。管道中的数据,读走就没了
  2. 只用于具有亲缘关系的进程之间通信
  3. 可看成一种特殊的文件,但只存在于内存中,但不是文件,

2.2原型

#include <unistd.h>
int pipe(int fd[2]);	//成功返回0,失败返回-1
  • 当一个管道建立时,他会创建两个文件描述符,fd[0]为读而打开,fd[1]为写而打开
  • 当需要写入数据时,必须先close(fd[0]),当需要读出数据时,必须先close[1],也就是同一时间只能读或者写

2.3实践

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


int main(){

	int fd[2] = {0};

	int pid;

	int fp;

	int status; 

	char wbuf[128] = "message from child";
	char rbuf[128] = {0};

	fp = pipe(fd);
	pid = fork();

	if(fp == -1){

		printf("creat pipe fail\n");
		exit(-1);
	}else{

		if(pid > 0){

			wait(&status);

			close(fd[1]);

			read(fd[0],rbuf,strlen(wbuf));

			printf("father:strlen(rbuf) = %ld\n",strlen(rbuf));

			printf("father:read message\n");

			printf("father:message = %s\n",rbuf);

			printf("status = %d\n",WEXITSTATUS(status));



		}else if(pid == 0){

			close(fd[0]);

			write(fd[1],wbuf,strlen(wbuf));

			printf("child:strlen(wbuf) = %ld\n",strlen(wbuf));

			printf("child:send message\n");

			exit(0);

		}else{
		
			printf("fork error\n");
		
		}	

	}



	return 0;

}
child:strlen(wbuf) = 18
child:send message
father:strlen(rbuf) = 18
father:read message
father:message = message from child
status = 0
ztj@ubuntu:~

三. FIFO

FIFO,也叫命名管道,它是一种文件类型

3.1特点

  1. FIFO可以在无关进程之间交换数据,与无名管道不同
  2. FIFO有路径名与之相关,它以一种特殊设备文件存在于文件系统中

3.2原型

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

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

  • 成功返回0,失败返回-1
  • mode与open相同,一旦创建FIFO,可以以一般文件i/o函数操作它

3.3 例子

  • 创建FIFO
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<errno.h>

int main(){

        if((mkfifo("./file",0600) == -1)&&(errno != EEXIST)){

                printf("mkfifo failuer\n");
                perror("why");

        }


        return 0;

}

ztj@ubuntu:~/part2/IPC$ ls
a.out  demo1.c  demo2.c  demo3.c  file
ztj@ubuntu:~/part2/IPC$ rm file
ztj@ubuntu:~/part2/IPC$ ls
a.out  demo1.c  demo2.c  demo3.c
ztj@ubuntu:~/part2/IPC$ ./a.out
ztj@ubuntu:~/part2/IPC$ ls
a.out  demo1.c  demo2.c  demo3.c  file
ztj@ubuntu:~/part2/IPC$ ./a.out

3.4 FIFO读写

  • open一个FIFO时,默认没有设置非阻塞标志,此时open会阻塞,只写open阻塞到有进程为了读而打开FIFO,只读open则相反。
  • 如果指定了非阻塞标志,只读open立即返回。只写open则出错返回-1,如果没有进程为读而打开FIFO,则其error置ENXIO。
  • read.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<errno.h>
#include <fcntl.h>
#include <unistd.h>

int main(){

	char buf[128] = {0};

	int nread;

	int fd = open("./file",O_RDONLY);

	if(fd != 0){
	
		printf("open success!!!\n");
	
	}

	while(1){
	
		nread = read(fd,buf,128);
	
		printf("read %d byte\n",nread);

		sleep(1);
	}



	close(fd);
	return 0;

}
  • write.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<errno.h>
#include <unistd.h>
#include <fcntl.h>


int main(){
	
	int fd;

	int nwrite;

	char buf[128] = "1232154874554654";

	if((mkfifo("./file",0600) == -1)&&(errno != EEXIST)){
	
		printf("mkfifo failuer\n");
		perror("why");
	
	}	

	fd = open("./file",O_WRONLY);

	if(fd != -1){
	
		printf("open success!!!\n");
	
	}

	while(1){
	
		nwrite = write(fd,buf,128);
	
//		printf("write %d byte\n",nwrite);

		sleep(1);

	}


	close(fd)
	return 0;

}

四.消息队列

消息队列,是消息的链接表,存放在内核中,一个消息队列有一个标识符来标识

4.1特点

  1. 消息队列,是面向记录的,其中的消息具有特定的格式和特定的优先级
  2. 独立于发送和接收进程,进程终止,其中的内容和消息队列不会删除
  3. 可以随机查询,不一定要先进先出次序读取,也可以按消息类型读取

4.2消息队列API

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

 - //创建或打开消息队列。成功返回队列ID,失败返回-1
 - int msgget(key_t key, int msgflg);
 - //添加消息,成功返回0,失败返回-1
 - int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
 - //读取消息,成功返回数据长度,失败返回-1
 - ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
 - //控制消息队列,成功返回0,失败返回-1
 - int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • 消息队列结构体:
struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};

  1. key:键值,
  2. msgsnd 和 msgrcv 中的 msgflag:标志符,写0,默认为读不到东西阻塞
  3. msgget 中的 msgflag:一般为IPC_CREAT |0777,代表可读写执行,没有则创建

4.3 key键值的获取和消息队列的移除

  1. ftok()获取键值
       #include <sys/types.h>
       #include <sys/ipc.h>

       key_t ftok(const char *pathname, int proj_id);
  • 第一个参数:指定已存在的文件名,一般选择当前目录“.”
  • 第二个参数ID:指子序列号,int型,但只是用8bit(0——255)
  • 也可以将文件的索引节点号取出,再加上子序列号,得到键值key(Linux下:ls -i查看索引节点号(ls -ai查看所有节点))
  1. 利用msgctl()移除队列,cmd传参,指针设为NULL就好。

4.4实践

  1. msgSnd.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<stdio.h>
#include<string.h>

struct msgbuf {
	long mtype;       /* message type, must be > 0 */
	char mtext[128];    /* message data */
};




int main(){

	 key_t key;
	     
     key = ftok(".",'x');

     printf("key = %x\n",key);


	struct msgbuf readbuf;

	struct msgbuf sendbuf = {888,"msg from msgSnd"};

	//1.打开/创建消息队列 :int msgget(key_t key, int msgflg);

	int msgId =  msgget(key, IPC_CREAT|0777);

	if(msgId == -1){

		printf("get que failuer\n");

	}

	//2.发送消息:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
	
	 msgsnd(msgId, &sendbuf, strlen(sendbuf.mtext), 0);

	 printf("msgsnd:%s\n",sendbuf.mtext);



	//3.接收消息: size_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

         msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),998,0);

	     printf("msgget: %s\n",readbuf.mtext);

   //4.移除队列 IPC_RMID int msgctl(int msqid, int cmd, struct msqid_ds *buf);

        msgctl(msgId, IPC_RMID, NULL);







	return 0;

}




  1. msgGet.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<stdio.h>
#include<string.h>

struct msgbuf {
	long mtype;       /* message type, must be > 0 */
	char mtext[128];    /* message data */
};




int main(){

    key_t key;

    key = ftok(".",'x');

     printf("key = %x\n",key);


	struct msgbuf readbuf;

	struct msgbuf sendbuf = {998,"msg from msgGet"};

	//1.打开/创建消息队列 :int msgget(key_t key, int msgflg);

	int msgId =  msgget(key, IPC_CREAT|0777);

	if(msgId == -1){

		printf("get que failuer\n");

	}

	//2.接收消息: size_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

         msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);

	 printf("msgget: %s\n",readbuf.mtext);


	//3.发送消息:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
	
	 msgsnd(msgId, &sendbuf, strlen(sendbuf.mtext), 0);

	 printf("msgsnd:%s\n",sendbuf.mtext);

   //4.移除队列 IPC_RMID int msgctl(int msqid, int cmd, struct msqid_ds *buf);

     msgctl(msgId, IPC_RMID, NULL);





	return 0;

}




五.共享内存

5.1解释

  • 内存中开辟的一段物理空间,这个空间,所有的进程都可以拿到地址,进行操作。

5.2共享内存通信

  • ipcs -m:查看系统中有哪些共享内存
  • ipcrm -m + id号:删除共享内存

5.3 API

       #include <sys/ipc.h>
       #include <sys/shm.h>
       1.//创建或获取一个共享内存,成功返回共享内存id,失败返回-1;size大小与M对齐
       2. int shmget(key_t key, size_t size, int shmflg);
       3.//连接共享内存到当前地址空间,成功返回共享内存指针,失败返回-1;第二,三个参数一般写0;代表系统为共享内存自动安排系统空间和共享内存可读可写
       4. void *shmat(int shmid, const void *shmaddr, int shmflg);
       5.// 断开与共享内存的连接,成功返回0,失败返回-1
       6. int shmdt(const void *shmaddr);
       7. //控制共享内存的相关信息,成功返回0,失败返回-1
       8. int shmctl(int shmid, int cmd, struct shmid_ds *buf);

5.4实践

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

int main(){

	int shmid;

	char *shmaddr;

	key_t key = ftok(".",1);

	//创建或获取一个共享内存,成功返回共享内存id,失败返回-1

	shmid =  shmget(key, 1024*4, IPC_CREAT|0666);
	if(shmid == -1){
		printf("shmget failuer\n");
		exit(-1);
	}

	//连接共享内存到当前地址空间,成功返回共享内存指针,失败返回-1;第二,三个参数一般写0

	shmaddr = shmat(shmid, 0, 0);

	strcpy(shmaddr,"曽铁江🐮🍺");
	//sleep(5);//等待另一端读取,并写入
	printf("%s\n",shmaddr);

	// 断开与共享内存的连接,成功返回0,失败返回-1

	shmdt(shmaddr);

	//控制共享内存的相关信息,成功返回0,失败返回-1

	shmctl(shmid, IPC_RMID, 0);


	return 0;
}

六.信号

6.1信号概述

  • 信号实际上是是软中断,为Linux提供了一种处理异步事件的一种方式。
  • 信号的名字和编号,名字都以SIG开头通过kill -l 查看,信号的编号是从1开始的,没有0,信号定义在signal.h中头文件中
ztj@ubuntu:~/part2/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

6.2信号处理的方式(忽略,捕捉和默认动作)

  • 忽略:大多数可以用这种方式处理,除了SIGKILL和SIGSTOP,因为它们向内核与超级用户提供的进程终止的可靠方法,不能忽略。

  • 捕捉信号:实际上用中断处理函数,当信号产生时,内核调用函数,实现信号处理

  • 默认动作:对应于每个信号,系统都有默认的处理方式。可以使用man 7 signal 来查看

  • 可以使用“kill -信号编号/信号名字 进程id ” 手动杀死一个进程,如:kill -9 4222

  • 信号处理函数:

     1.入门版:signal
     2.高级版:sigaction
    
  • 信号发送函数:

     1.入门版:kill
     2.高级版:sigqueue
    

6.3信号编程

6.3.1 kill 和 signal

       #include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);
       #include <sys/types.h>
       #include <signal.h>

       int kill(pid_t pid, int sig);
  • sigget.c
#include<stdio.h>
#include <signal.h>

void sighandler(int signum){

	switch(signum){

		case 9:		
			printf("SIGKILL\n");
			break;
		case 2:
			printf("SIGINT\n");
			break;	
	}

	printf("never quit\n");

}


int main(){
	
		
	signal(SIGINT,sighandler);
	signal(SIGKILL, sighandler);

	while(1);
	return 0;

}

  • sigsnd.c
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>


int main(int argc,char **argv){

	int  signum;//信号的编号
	int pid;//目标进程的pid
	char cmd[128] = {0};

	signum =atoi(argv[1]);//注意:atoi 将字符转换为int型
	pid =atoi(argv[2]);

	sprintf(cmd,"kill -%d %d",signum,pid);//构造system()所需的参数

//	kill(pid,signum);
	
	system(cmd);//如果不用system,也可以直接用kill发送信号


	return 0;

}
  • 补充:可以使用SIG_IGN,忽略某个信号(除SIGKILL和 SIGSTOP),例如:signal(SIGINT,SIG_IGNA),将信号SIGINT忽略了

6.3.2 sigqueue 和 sigaction

6.3.2.1 sigqueue()
       #include <signal.h>

       int sigqueue(pid_t pid, int sig, const union sigval value);

  • pid:发给谁
  • sig:信号编码
  • value:要发送的消息
6.3.2.2 sigaction()
  • 原型
       #include <signal.h>

       int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

  • signum:信号编码
  • act:结构体指针,指向如下结构体
  struct sigaction {
       void     (*sa_handler)(int); //信号处理程序,不接受额外数据,与signal的参数相同,一般默认设为SIG_IGN忽略
       void     (*sa_sigaction)(int, siginfo_t *, void *);//信号处理程序(函数),接收额外数据
       sigset_t   sa_mask;//阻塞关键字的信号集,默认为阻塞
       int        sa_flags;//影响接收数据的行为,SA_SIGINFO表示能够接收数据,需要手动设置
       void     (*sa_restorer)(void);//不用于应用程序,不用管
  };

   sa_sigaction:
   					参数int:num,用于接收signum
   					参数siginfo_t*:结构体
   					参数void*:一个指针,当指针为空的时候表示无数据,非空表示有数据,用于判断  
	signfo_t:
			内容1:pid,谁发的
			内容2:si_int,整型数
			内容3:si_value,也是一个含数据的联合体,支持int和char*数据
			..........(具体man 手册查看)

  • oldact:结构体指针,用来备份原来的信号操作,一般我们不关心备份的话,写NULL就可以了
6.3.2.3实战
  • sigactionpro.c
#include<signal.h>
#include<stdio.h>

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



}

int main(){

	int signum = 2;

	struct sigaction act;
	act.sa_sigaction = handler;
	act.sa_flags = SA_SIGINFO;	

	sigaction(signum,&act,NULL);


	while(1);
	return 0;

}
  • sigqueuepro.c
#include<signal.h>
#include<stdio.h>
#include <stdlib.h>
/*union sigval {
       int   sival_int;
       void *sival_ptr;
};*/




int main(int argc,char **argv){

	int signum = atoi(argv[1]);
	pid_t pid = atoi(argv[2]);

	union sigval value;
	value.sival_int = 123456789;
	
	sigqueue(pid,signum, value);


	return 0;

}

七.信号量

信号量和 IPC 的结构不同,它是一个计数器。用于实现进程间的同步和互斥,而不是存储进程间的通信数据

7.1信号量概述

  • 特点:
  • 信号量用于进程间的同步,传递数据就要结合共享内存
  • 多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。
  • 信号量基于系统的PV操作(拿锁和放锁)。
  • 每次PV操作可以加任意正整数
  • 支持信号量组

7.2信号量编程

Linux信号量函数都是在信号量组上进行操作,而不是在单一的二值(0/1)信号量操作

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
	  1.//创建和获取一个信号量组,成功返回信号量集ID,失败返回-1	
      2. int semget(key_t key, int nsems, int semflg);
      3. //对信号量进行操作,改变信号量的值,成功返回0,失败返回-1
      4. int semop(int semid, struct sembuf *sops, size_t nsops);
      5. //控制信号量的相关信息,第二个参数为:第几个信号量(0开始)
      6. int semctl(int semid, int semnum, int cmd, ...);
		
  • 当semctl有四个参数时,第四个参数为联合体:
           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) */
           };

  • 实战
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<stdio.h>
#include <unistd.h>


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;//第几位信号量,注意,编号从零开始
	sops.sem_op = -1;//操作+1/-1
	sops.sem_flg =SEM_UNDO;//标志符IPC_NOWAIT/SEM_UNDO


	semop(semid,&sops,1);//sops是一个结构体数组,这里只有一个结构体,所以nsops = 1(第三个参数)
	
	printf("get key\n");

}

void vputbackkey(int semid){


	struct sembuf sops;
	
	sops.sem_num = 0;//第几位信号量
	sops.sem_op = 1;//操作+1/-1
	sops.sem_flg =SEM_UNDO;//标志符IPC_NOWAIT/SEM_UNDO


	semop(semid,&sops,1);//sops是一个结构体数组,这里只有一个结构体,所以nsops = 1(第三个参数)
	
	printf("put back key\n");

}


int main(){

	int pid;

	key_t key;
	key = ftok(".",2); 

	int semid;
	union semun initsem;//联合体,根据semctl第三个参数选择,类型要自己定义
	initsem.val = 0;//开始是无锁的状态

	semid = semget(key,1, IPC_CREAT|0666);//信号量的个数为1
	semctl(semid,0,SETVAL,initsem);//0第几个信号量

	pid = fork();
		if(pid >0){

			pgetkey(semid);
			printf("this is father\n");
			vputbackkey(semid);

			semctl(semid,0,IPC_RMID);//销毁第零个信号量

		}else if(pid == 0){
		//	pgetkey(semid);
			printf("this is child\n");
			vputbackkey(semid);

		}else{

			printf("fork error\n");

		}	

	return 0;

}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

elaot

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值