Linux 进程间通信编程

一、进程间通信概述

1、目的:
1)数据传输:
一个进程需要将它的数据发送给另一个进程
2)资源共享
多个进程之间共享同样的资源
3)通知事件
一个进程需要向另一个或一组进程发送消息,通知他们发生了某种事件
4)进程控制
有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所以操作,并能够及时知道它的状态改变

2.通信方式:
管道(pipe)和有名管道(FIFO)
信号(signal)
消息队列
共享内存
信号量
套接字(socket)

二、管道通信(默认无名管道)

管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据

无名管道 只有有亲缘关系的进程来使用 因为管道无名很难找到 所以如果父进程创建管道 然后创建子进程 子进程可以继承父进程的变量 才能找到管道

头部与尾部用文件描述符来表示

**数据被一个进程读出后,将被从管道中删除,其他读进程将不能在读到这些数据。**管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞,同样,管道已满时,进程再试图向管道写入数据,进程将阻塞

1.创建无名管道函数pipe()

头文件是#include<unistd.h>
在这里插入图片描述
创建成功返回0 不成功返回-1
读使用read( ) 写使用write( )

在这里插入图片描述

上图 需要关闭父进程的读 关闭子进程的写 使它成为单向

2.使用无名管道读取的代码练习

父进程向无名管道写入小写字母 子进程读出后转换为大写字母写入管道 父进程再将其读出 具体代码(可运行)

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

int main(){
	int fd[2];
	pid_t pid;
	char buffer[100];//缓存区
	int ret,i;
	int ret_num;

	memset(buffer,0,sizeof(buffer));//清空缓存区

	ret = pipe(fd);//创建无名管道 返回0表示创建成功

	if(ret == -1){
		perror("pipe error!\n");
		exit(-1);
	}

	pid = fork();//创建子进程
	if(pid < 0){//创建失败关闭管道出入口
		perror("fork error!\n");
		close(fd[0]);
		close(fd[1]);
		exit(-1);
	}
	else if(pid > 0){//父进程
		if((write(fd[1],"hello",5)) != -1){//向无名管道中写入hello  fd【1】是管道写端
			printf("father write success!\n");
		}

		if((write(fd[1],"pipe",5)) != -1){//再次写
			printf("father write success!\n");
		}

		wait(NULL);//等待子进程结束

		read(fd[0],buffer,10);//读取管道内容进入缓冲区
		printf("father read butter:%s\n",buffer);
		close(fd[0]);
		close(fd[1]);
	}

	else{//子进程
		if((ret_num = read(fd[0],buffer,10)) != -1){//读取管道中的内容 存放在缓存区 成功 返回读取个数
			printf("child:read num is %d , buffer:%s\n",ret_num,buffer);
		}

		for(i = 0;i < ret_num -1;i++){
			buffer[i] = toupper(buffer[i]);//将缓冲区小写转为大写
		}

		write(fd[1],buffer,ret_num);//将缓冲区内容写入管道

		close(fd[0]);
		close(fd[1]);
	}
	return 0;

}

三、命名管道

命名管道和无名管道基本相同,但也有不同点:无名管道只能由有血缘关系的进程使用;但通过命名管道,不相关的进程也能交换数据 因为可以根据名字找到管道 无需继承

1.创建有名管道函数mkfifo()

在这里插入图片描述

第一个参数 管道名 其余进程 可通过管道名打开管道
第二个参数 常见属性:S_IRUSE 可读 S_IWUSR可写 S_IXUSR可执行 S_IXRWU 可读可写可执行

int mkfifo(const char * pathname,mode_t mode);
函数说明

mkfifo()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限(mode%~umask),因此 umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。当使用open()来打开 FIFO文件时,O_NONBLOCK旗标会有影响

1、当使用O_NONBLOCK 旗标时,打开FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开FIFO 文件来读取,则写入的操作会返回ENXIO 错误代码
例 fd = open (FIFO_SEVER,O_RDONLY|O_NONBLOCK);

2、没有使用O_NONBLOCK 旗标时,打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回

打开方式 与文件相同 头文件#include<fcntl.c>
fd = open(FIFO_SEVER,O_WRONLY);

2.有名管道 无关系进程之间的读取代码练习

a.写入的代码

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include<memory.h>
#include<unistd.h>

#define FIFO_SEVER "/home/2022/0305/fifosever"

int main(){
	int fd;
	int ret;
	char w_buf[1000];
	memset(w_buf,0,1000);

	if((mkfifo(FIFO_SEVER,O_CREAT|O_EXCL) < 0) && (errno != EEXIST)){
		printf("cannt create fifosever!\n");
		exit(0);
	}

	fd = open(FIFO_SEVER,O_WRONLY);

	if( fd == -1){
		if(errno == ENXIO)
            printf("open error,no reading process\n");
	}
	printf("please input \n");
	scanf("%s",w_buf);

	ret = write(fd,w_buf,strlen(w_buf));
	if(ret == -1){
		printf("write fifo error try later!\n");
	}
	else{
		printf("write fifo success ret num is %d\n",ret);
	}
	return 0;
}

b.读取的代码

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <errno.h>
#include<memory.h>
#include<unistd.h>

#define FIFO_SEVER "/home/2022/0305/fifosever"

int main(){
	int fd;
	char r_buf[1000];
	int ret;


	fd = open(FIFO_SEVER,O_RDONLY);

	if(fd == -1){
		printf("open fifosever fail!\n");
		exit(0);
	}
		memset(r_buf,0,sizeof(r_buf));
		ret = read(fd,r_buf,100);

	if(ret == -1){
			if(errno == EAGAIN)
			printf("no data avalible\n");
	}
	else{
		printf("read fifo success! read is %s\n",r_buf);
		sleep(1);
	}

	unlink(FIFO_SEVER);

	return 0;
}

四、信号通信

1.信号机制

信号机制是Unix系统中最为古老的进程间通讯机制,很多条件可以产生一个信号:
1.当用户按某些按键时,产生信号
2.硬件异常产生信号:除数为0、无效的存储访问等等。这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程,例如,内核对正在访问一个无效存储区的进程参数一个SIGSEGV信号
3。进程用kill函数将信号发送给另一个进程
4.用户可用kill命令将信号发送给其他进程

2.信号发送kill函数与raise函数

在这里插入图片描述

raise默认发给自己

kill第一个参数是指定发生给谁
如果>0 就是指定的进程的ID
=0 把这个信号发生给这个进程所在进程组的每一个进程
<-1 取绝对值 看哪个进程组的ID等于这个绝对值 然后发送信号给这个进程组中所有进程
=-1 发送给除自己以外 所有的进程

当第二个参数 写0 没有信号发送 因为64个信号中没有序号为0的
没有错 意义是检测我有没有权限向指定的pid发送信号

3.alarm函数

在这里插入图片描述
注意alarm函数 如果没有给予执行动作的话 默认动作是终止进程

每个进程只能有一个闹钟时间,如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,以前登记的闹钟时间则被新值代换
如果有以前等级的尚未超过的闹钟时间,而这次seconds值是0,则表示取消以前的闹钟

alarm函数可以写多个 但程序运行时 只能有一个生效
并不是只能写一个 可以错峰使用alarm

返回值是没进行完的剩余时间

4.pause函数

在这里插入图片描述

5.信号处理:

1.忽略信号

2.执行用户希望的动作
通知内核再某种信号发送时,调用一个用户函数,再用户函数中,执行用户希望的处理

3.执行系统默认动作
对大多数信号的系统默认动作是终止该进程

在这里插入图片描述
其中信号集函数组不常用

6.signal信号处理函数

在这里插入图片描述

第一个参数 是想捕捉哪个信号
第二个参数是指向函数的指针
所以要传 你想要调用的执行函数的地址(函数名)或者SIG_IGN表示忽略此信号 或者SIG_DFL表示系统默认方式处理(一般系统默认方式 是结束此进程)

7signal处理信号的具体实例代码:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>

void func(int signal){
	if(signal == SIGINT){//当接收到的信号是SIGINT时 执行printf中的语句
		printf("I get SIGNAL!\n");
	}
	else if(signal == SIGQUIT){//同上
		printf("I get SIGQUIT!\n");
	}
}

int main(){//使用signal函数 处理信号 执行自己想要的动作 即func函数
	printf("waiting for signal SIGINT or SIGQUIT!\n");

	signal(SIGINT,func);//当接收到SIGINT信号后 执行func里的动作
	signal(SIGQUIT,func);//同上
	
	pause();//进程阻塞 等待一个信号的产生 进程才会终止
	return 0;
}

执行结果:
在这里插入图片描述

9.signal函数信号处理和kill函数信号发送练习代码

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

int main(){
	pid_t pid;
	int select;

	void func(int signal){
		if(signal == SIGINT){
			printf("I get SIGINT!\n");
		}
		else if(signal == SIGQUIT){
			printf("I get SIGQUIT!\n");
			exit(1);
		}
	}

	pid = fork();

	if(pid == 0){
		signal(SIGINT,func);
		signal(SIGQUIT,func);

		pause();
	}
	else{

		printf("please input your select 1 or 2\n");
		scanf("%d",&select);

		if(select == 1){
			kill(pid,SIGINT);//向pid这个进程发送ISGINT这个信号
		}
		else{
			kill(pid,SIGQUIT);
		}
		sleep(1);//让父进程睡1s 防止子进程成孤儿进程
	}	

	return 0;
}

五、共享内存

共享内存是被多个进程共享的一部分物理内存,共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。

1.共享内存使用步骤

1.创建共享内存函数shmget( )
2.映射内核中的共享内存的地址到进程中去 函数为shmat( )
3.使用共享内存 怎么用 直接用
4.有进程不用再使用共享内存 不使用共享内存的进程 用shmdt ( )解除映射
5.所有进程都不用了 有一个进程删除共享内存就可以 使用shmctl( )

2.创建共享内存

在这里插入图片描述
key 键值 指向我想创建共享内存的id(相当于给要创建的共享内存的文件名 而返回值是文件描述符)
用于区分不同的共享内存 创建成功后就再也不使用key值了 而使用函数返回的ID值即共享内存标识符。

第二个参数 指定共享内存大小 大小在4k以下 系统都是给4k个字节
第三个参数 表示创建的共享内存的模式 IPC_CREAT不存在就创建
加上|IOC_EXCL表示存在就出错 也可以加权限

在这里插入图片描述

3.映射共享内存

在这里插入图片描述
第一个参数就是共享内存标识符
第二个参数 是字节地址 是将共享内存的起始地址放在这个参数中 多数情况下写NULL 这是让系统帮我在进程中找个地址存放映射共享内存的地址,而函数返回值 就是这个共享内存映射的地址 以后使用共享内存 就用这个返回值;
第三个参数 取0 表示映射了共享内存在我的进程中是可读可写的

如果映射失败 返回-1 所以以后使用这个函数 不能将返回值直接跟-1比
因为-1是整数 而返回值是char 要将-1 强转为void**

4.解除映射

在这里插入图片描述
解除映射函数参数是 映射函数返回值;

5.共享内存代码练习题

父进程向共享内存输入字符 子进程判断是否是自己要接收的信息 是的话 输出共享内存中的信息

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

#define BUFFER_SIZE 2048

int main(){
	pid_t pid;
	int shmid;//存放共享内存描述符 类似与文件描述符
	char *shm_addr;//用于存放映射共享内存的地址
	char flag[] = "parent";//后期将其放入共享内存中 用以区分是否是子进程要接收的信息
	char buff[BUFFER_SIZE];//缓存区

	if((shmid = shmget(IPC_PRIVATE,BUFFER_SIZE,0666)) < 0){//创建共享内存
		perror("shmget!\n");
		exit(1);
	}
	else{
		printf("create share memory success!\n");
	}

	pid = fork();//创建子进程

	if(pid < 0){
		perror("fork!\n");
		exit(1);
	}
	else if(pid == 0){//子进程
		shm_addr = shmat(shmid,NULL,0);//映射共享内存
		if(shm_addr == (void*)-1){//判断是否映射成功
			perror("shmat!\n");
			exit(1);
		}
		else{
			printf("child:Attach share memory success address is:%p\n",shm_addr);//输出映射地址
		}

		while(strncmp(shm_addr,flag,strlen(flag))){//比较共享内存信息是否是子进程要接收的
			printf("child:not my data waiting later...\n");//不等于0的话 睡3s等待
			sleep(3);
		}

		strcpy(buff,shm_addr + strlen(flag));//是为要接收的信息 跳过parent这个字符串 接收其后面的信息
		printf("child: share memory:%s\n",buff);//输出共享内存的信息

		if(shmdt(shm_addr) < 0){//解除映射
			perror("child:shmdt error!\n");
			exit(1);
		}
		else{
			printf("child:detached share memory\n");
		}
	}
	else{//父进程
		sleep(1);
		if((shm_addr = shmat(shmid,NULL,0)) == (void*)-1){//映射共享内存
			perror("father:shmat error!\n");
			exit(1);
		}
		else{
			printf("father attach share memory is %p\n",shm_addr);//输出映射地址
		}

		sleep(1);
		printf("input string:\n");

		fgets(buff,BUFFER_SIZE - strlen(flag),stdin);//输入信息进入缓存区 要去掉parent的大小 因为共享内存空间需要存在parent字符串 不能完全输入缓存区的全部大小

		strncpy(shm_addr + strlen(flag),buff,strlen(buff));//给parent字符串留位置 向共享内存输入信息
		strncpy(shm_addr,flag,strlen(flag));//向共享内存开头输入parent字符串

		if(shmdt(shm_addr) < 0){//解除映射
			perror("father:shmdt error!\n");
			exit(1);
		}
		else{
			printf("father:detached share memory\n");
		}

		waitpid(pid,NULL,0);//等待子进程结束

		if(shmctl(shmid,IPC_RMID,NULL) == -1){//释放共享内存
			perror("shmtlc error!\n");
			exit(1);
		}
		else{
			printf("delete share memory\n");
		}
	}
	return 0;
}

6.无血缘关系的进程使用共享内存通信代码练习

将共享内存类型转换为结构体类型 其中两个成员一个是written用以区分是读还是写 还有一个用来存放信息

共享内存结构体

#define TEXT_SZ 2048

struct shared_use_st
{
    int written;
    char text[TEXT_SZ];
};

写进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/shm.h>
#include "shmdata.c"
#include <string.h>

#define BUFFSIZE 2048

int main()
{
    int running = 1;
    void *shm = NULL;//存放映射共享内存地址
    struct shared_use_st * shared = NULL;//定义共享内存结构体类型指针
    char buffer[BUFFSIZE + 1];
    int shmid;
    
    shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);//有就打开 没有就创建

    if(shmid == -1)
    {
        fprintf(stderr,"shmget failed\n");
        exit(EXIT_FAILURE);
    }
    shm = shmat(shmid,0,0);// 映射共享内存
    if(shm == (void*)-1)
    {
        fprintf(stderr,"shmat failed\n");
        exit(EXIT_FAILURE);
    }
    printf("\n memary attached at %p\n",shm);

    shared = (struct shared_use_st *)shm;//将映射地址强转为共享内存结构体类型 并让share指向它
    //shared->written = 0;
    while(running)
    {
        while(shared->written == 1)//当wirtten为1时表示是另一个进程正在读共享内存 此时需要等待
        {
            sleep(1);
            printf("waitting...\n");
        }
        while(running){
        	printf("Enter some text:\n");
        	fgets(buffer,BUFFSIZE,stdin);// 向缓存区写入数据
        	strncpy(shared->text,buffer,TEXT_SZ);//将缓存区的信息写入共享内存
      
      	shared->written = 1;//写完 将written改为1 这要另一个进程得到written为1表示它可以读了
         	if(strncmp(shared->text,"end",3) == 0)//如果输入end 结束循环
        	running = 0;
        }
    }
    if(shmdt(shm) == -1)//解除映射
    {
        fprintf(stderr,"shmdt failed\n");
        exit(EXIT_FAILURE);
    }
    sleep(2);
    exit(EXIT_SUCCESS);
    return 0;
}

读进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/shm.h>
#include "shmdata.c"
#include <string.h>


int main()
{
    int running = 1;
    void *shm = NULL;
    struct shared_use_st * shared;
    int shmid;
    
    shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);//有1234这个共享内存就打开 没有就创建

    if(shmid == -1)
    {
        fprintf(stderr,"shmget failed\n");
        exit(EXIT_FAILURE);
    }
    shm = shmat(shmid,0,0);
    if(shm == (void*)-1)
    {
        fprintf(stderr,"shmat failed\n");
        exit(EXIT_FAILURE);
    }
    printf("\n memary attached at %p\n",shm);

    shared = (struct shared_use_st *)shm;
    shared->written = 0;
    while(running)
    {
        if(shared->written != 0)
        {
            printf("you wrote : %s\n",shared->text);
            sleep(rand()%3);
            shared->written = 0;//读完将weitten改为0 另一个进程得到written=0 就可以写了
            if(strncmp(shared->text,"end",3) == 0)
                running = 0;
        }
        else
            sleep(1);
    }
    if(shmdt(shm) == -1)
    {
        fprintf(stderr,"shmdt failed\n");
        exit(EXIT_FAILURE);
    }
    if(shmctl(shmid,IPC_RMID,0) == -1)
    {
        fprintf(stderr,"stmctl failed\n");
        exit(EXIT_FAILURE);
    }
    
    exit(EXIT_SUCCESS);
    return 0;
}

六、消息队列

在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第一个参数是消息队列的ID即创建时返回值
第二第三个参数是要配合的 第二个参数给出了要开始写的地址 第三个参数表示写多少个字节

第四个参数 是flag 不需要写0(阻塞)
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
查参数

释放

七、信号量

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Alex、WY

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

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

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

打赏作者

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

抵扣说明:

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

余额充值