Linux 进程间通信编程(单机)

  • 一、进程间通信概述
  • 二、管道通信(默认无名管道)
    • 1.创建无名管道函数pipe()
    • 2.使用无名管道读取的代码练习
  • 三、命名管道
    • 1.创建有名管道函数mkfifo()
    • 2.有名管道 无关系进程之间的读取代码练习
      • a.写入的代码
      • b.读取的代码
  • 四、信号通信
    • 1.信号机制
    • 2.信号发送kill函数与raise函数
    • 3.alarm函数
    • 4.pause函数
    • 5.信号处理:
    • 6.signal信号处理函数
    • 7signal处理信号的具体实例代码:
    • 9.signal函数信号处理和kill函数信号发送练习代码
  • 五、共享内存
    • 1.共享内存使用步骤
    • 2.创建共享内存
    • 3.映射共享内存
    • 4.解除映射
    • 5.共享内存代码练习题
    • 6.无血缘关系的进程使用共享内存通信代码练习
  • 六、消息队列
  • 七、信号量

一、进程间通信概述

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 2048int 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 2048struct 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 2048int 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;}elsesleep(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(阻塞)



查参数

释放

七、信号量

Linux 进程间通信编程相关推荐

  1. Linux进程间通信编程

    一.进程间通信概述 基本目的: 1.数据传输     一个进程需要将它的数据发送给另一个进程 2.资源共享     多个进程之间共享同样的资源 3.通知事件     一个进程需要向另一个或一组进程发送 ...

  2. Linux网络编程--进程间通信(一)

    进程间通信简介(摘自<Linux网络编程>p85) AT&T 在 UNIX System V 中引入了几种新的进程通讯方式,即消息队列( MessageQueues),信号量( s ...

  3. linux进程间通信:system V 信号量 生产者和消费者模型编程案例

    生产者和消费者模型: 有若干个缓冲区,生产者不断向里填数据,消费者不断从中取数据 两者不冲突的前提: 缓冲区有若干个,且是固定大小,生产者和消费者各有若干个 生产者向缓冲区中填数据前需要判断缓冲区是否 ...

  4. 实验六 Linux进程编程,Linux系统编程实验六:进程间通信

    <Linux系统编程实验六:进程间通信>由会员分享,可在线阅读,更多相关<Linux系统编程实验六:进程间通信(10页珍藏版)>请在人人文库网上搜索. 1.实验六:进程间通信l ...

  5. 【Linux系统编程】进程间通信之无名管道

    00. 目录 文章目录 00. 目录 01. 管道概述 02. 管道创建函数 03. 管道的特性 04. 管道设置非阻塞 05. 附录 01. 管道概述 管道也叫无名管道,它是是 UNIX 系统 IP ...

  6. 【Linux系统编程】进程间通信概述

    00. 目录 文章目录 00. 目录 01. 进程间通信概述 02. 进程间通信目的 03. 进程间通信机制 04. 附录 01. 进程间通信概述 进程是一个独立的资源分配单元,不同进程(这里所说的进 ...

  7. 【Linux | 系统编程】Linux系统编程(文件、进程线程、进程间通信)

    文章目录 Linux系统编程 文件IO open/close函数 read/write函数 文件描述符 阻塞.非阻塞 fcntl函数 lseek函数 传入传出参数 文件系统 文件存储 文件操作 sta ...

  8. Linux系统编程【文件IO、进程、进程间通信、信号、线程、互斥】

    linux系统编程 个人通过学习,手打了一份48000字的Linux系统编程的笔记,包含了[文件IO.进程.进程间通信.信号.多线程.互斥]等知识点,并给出了大量的代码案例对每个重要的知识点进行了代码 ...

  9. c语言系统编程八:Linux进程间通信之消息队列

    Linux进程间通信之消息队列 一 消息队列概述 二 消息队列的特点 三 消息队列的创建和使用 3.1 获取系统唯一的key值 3.2 创建消息队列 3.3 查看消息队列和删除消息队列的shell命令 ...

最新文章

  1. codevs——2894 Txx考试(背包)
  2. Xcode 环境变量(绝对路径与相对路径)
  3. make 编译可执行
  4. NDK 与 JNI 的关系
  5. Vue.js 状态管理
  6. 树莓派实现AD转换(pcf8591模块)
  7. leetcode-49-字母异位词分组
  8. 设置 Xcode 自动生成代码片段
  9. python request url编码_Python 爬虫 (requests) 发送中文编码的 HTTP POST 请求
  10. HomeBrew 更换为国内源--提高brew命令操作速度
  11. 数据结构----出栈顺序有效性的判断
  12. Android之notificaction使用
  13. 【三维路径规划】基于matlab遗传算法无人机三维路径规划【含Matlab源码 1526期】
  14. 关于指针赋初值为NULL的问题
  15. (中英)作文 —— 标题与小标题
  16. spring boot中的banner制作
  17. 无线ap安全dhcp服务器,AC+AP时代——办公区内满足安全性又够人性化的WiFi漫游设置攻略...
  18. 适合苹果的蓝牙耳机推荐,音质超好的蓝牙耳机推荐
  19. 基于JSP技术的游泳馆管理系统
  20. 原谅帽大作战游戏程序

热门文章

  1. VS+Qt应用开发-Qt+Halcon显示图片,实现鼠标缩放、移动图片
  2. python frozenset_Python中set与frozenset方法和区别详解
  3. 调薪之后该思考的问题
  4. ps cc2018启动界面无响应解决方案
  5. Java实现 LeetCode 799 香槟塔 (暴力模拟)
  6. 贵州大学计算机科学与技术学院电子信息,贵州大学计算机科学与技术学院官网...
  7. 手机显示android怎么办,安卓手机卡顿怎么办?你必须要知道的办法!
  8. oauth2 feign 报401的错误
  9. 用python画多个圆_Python用图例在网格上绘制多个圆
  10. @webservlet注解详解