Linux系统编程——进程间通信

一. 概述

1.1 单机通信

  • 半双工管道 (无名管道)
  • 全双工管道FIFO(命名全双工管道)
  • 消息队列
  • 信号量
  • 共享存储

1.2多机通信:

6.套接字
7.STREAMS

管道通信

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

2.1 特点

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

2.2原型

## Linux 快捷指令
man 2 pipe

在这里插入图片描述

  • 当一个管道建立时,他会创建两个文件描述符,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特点

  • FIFO可以在无关进程之间交换数据,与无名管道不同
  • 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;

}

执行结果

在这里插入图片描述

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查看所有节点))

2.利用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;

}


2.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中头文件中
    在这里插入图片描述

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,也是一个含数据的联合体,支持intchar*数据
			..........(具体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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值