IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux系统编程系列(三) -> 正文阅读

[系统运维]Linux系统编程系列(三)

进程间通信

由于进程间的地址空间相对独立。进程与进程间不能像线程间通过全局变量通信,所以进程之间要交换数据必须通过内核。
在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信;

1、相关概念

(1)进程间通信:InterProcess Communication

(2)进程间通信常用的4种方式
a.管道-简单
b.信号-系统开销小
C.共享映射区-(有无血缘关系的进程间通信都可以)
d.本地套接字-稳定

2、管道pipe

1.管道的概念

本质:

内核缓冲区

伪文件-不占用磁盘空间

特点:

两部分:

读端,写端,对应两个文件描述符

数据写端流入,读端流出

操作管道的进程被销毁之后,管道自动被释放

管道默认是阻塞的。

2、管道的原理

内部实现方式:队列

环形队列,先入先出;

缓冲区大小默认是4K(非绝对),根据实际需要适当调整

3、管道的局限性

队列:数据只能读取依次,不能重复读取;

半双工:读写只能一端进行,另一端等待,具有固定的读端与写端;

无名/匿名管道:

  • 只能在亲缘关系的进程中进行(父子、兄弟、祖孙等);
  • 文件中找不到它的存在,只存在于内存中;
  • 不能用lseek来定位
  • 具备阻塞(有读者有写者,当读没有数据或者是写满了的时候会卡在那里等待)

4、创建pipe管道

用于亲缘进程通信

#include <unistd.h>

int pipe(int pipefd[2]);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <fcntl.h>              /* Obtain O_* constant definitions */
#include <unistd.h>

int pipe2(int pipefd[2], int flags); // 
/*
flags 包括O_CLOEXEC、O_DIRECT、O_NONBLOCK。
- CLOEXEC:close-on-exec即当调用exec()函数成功后,文件描述符会自动关闭
- O_DIRECT:任何读写操作都只在用户态地址空间和磁盘之间传送而不经过page cache
- O_NONBLOCK:非阻塞模式,在读取不到数据或是写入缓冲区已满会马上return,而不会阻塞等待。
*/

int pipe(int pipefd[2]); 成功:0;失败:-1,设置errno

函数调用成功返回r/w两个文件描述符。无需open,但需手动close。

规定:fd[0] → r; fd[1] → w,就像0对应标准输入,1对应标准输出一样。

向管道文件读写数据其实是在读写内核缓冲区

在这里插入图片描述

查看pipe函数的输出位置

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

int main(int argc, const char* argv[]) {
        int fd[2];//创建一个数组
        int ret = pipe(fd);//创建管道
        if (ret == -1) {
                perror("pipr error");
                exit(1);
        }//错误提示
		pid_t pid = fork();//上面建立了父进程,此处增加一个子进程;
        printf("pipe[0] = %d\n", fd[0]);//输出读端位置
        printf("pipe[1] = %d\n", fd[1]);//输出写端位置
        /*
        因为在PCB中前三位分别被标准输入,标准输出、标准错误占据,所以读段与写端分别输出为
        pipe[0] = 3
		pipe[1] = 4
        */
    
    	close(fd[0]);
        close(fd[1]);

        return 0;
}


思考题:

  1. 单个进程能否使用管道完成读写操作?

    可以,见上面的程序

  2. 父子进程间通信是否需要sleep函数?
    父写-写的慢
    子读-读的快

  3. 注意事项

    • 父进程读

      —关闭写端

    • 子进程写

      —关闭读端

在这里插入图片描述

练习:

1、父子进程间通信,实现ps aux|grep bash
数据重定向: dup2
execlp

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

int main(int argc, const char* argv[]) {
	int fd[2];
	int ret = pipe(fd);
	if (ret == -1) {
		perror("pipr error");
		exit(1);
	}

	pid_t pid = fork();
	if (pid == -1) {
		perror("fork");
		exit(1);
	}

	//父进程 ps aus
	if (pid > 0) {
		//父进程进行写管道的操作,需要关闭读端;
		close(fd[0]);
		//文件描述符重定向,指向管道的写端 
		//stdout_fileno->管道的写端
		dup2(fd[1], STDOUT_FILENO);
		//执行ps aux
		execlp("ps","ps", "aux", NULL);
		perror("execlp");
		exit(1);
	}else if (pid == 0) {  //子进程 grep bash
		//子进程进行读管道的操作,需要关闭写端;
		close(fd[1]);
		//文件描述符重定向,指向管道的读端 
		//STDIN_FILENO->管道的读端
		dup2(fd[0], STDIN_FILENO);
		//执行ps aux
		execlp("grep", "grep", "bash", NULL);
		perror("execlp");
		exit(1);
	}
	close(fd[0]);
	close(fd[1]);

	return 0;
}

2、兄弟进程间通信,实现ps aux | grep bash
父亲-资源回收

对本地画图分析,现在仅需要对子进程1进行写操作,进程2进行读操作,其他的管道都要关掉。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0JXO5ZRv-1627220625711)(D:\学习资料\C and C++\每日记录\Linux系统编程.assets\image-20210720202822147.png)]

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

int main(int argc, const char* argv[]) {
	int fd[2];
	int ret = pipe(fd);
	if (ret == -1) {
		perror("pipr error");
		exit(1);
	}

	pid_t pid;
	int i = 0;//设置循环因子
	for ( i= 0; i < 2; i++) {
		pid = fork();
		if (pid == 0) {
			break;
		}
	}

	if (pid == -1) {
		perror("fork");
		exit(1);
	}

	//子进程1 ps aus
	if (i==0) {
		//子进程1进行写管道的操作,需要关闭读端;
		close(fd[0]);
		//文件描述符重定向,指向管道的写端 
		//stdout_fileno->管道的写端
		dup2(fd[1], STDOUT_FILENO);
		//执行ps aux
		execlp("ps","ps", "aux", NULL);
		perror("execlp");
		exit(1);
	}
	
	if (i == 1) {  //子进程2 grep bash
		//子进程2进行读管道的操作,需要关闭写端;
		close(fd[1]);
		//文件描述符重定向,指向管道的读端 
		//STDIN_FILENO->管道的读端
		dup2(fd[0], STDIN_FILENO);
		//执行ps aux
		execlp("grep", "grep", "bash", NULL);
		perror("execlp");
		exit(1);
	}
    
    //父进程
    if(i==2){
        //双端都要关掉
        close(fd[0]);
        close(fd[1]);
        //回收子进程
        //用while循环避免遗漏
        pid_t wpid;
        while((wpid = waitpid(-1,NULL,WNOHANG))!=-1){
        if (wpid == 0) {
			continue;
		}
		printf("child died pid= %d\n", wpid);
        }
    }

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

	return 0;
}

5、管道的读写操作

  1. 读操作

    1. 有数据

      • read(fd) 正常读,返回读出的字节数
    2. 无数据

      • 写端全部关闭

      • read解除阻塞,返回0

      • 相当于读文件读到了尾部

      • 没有全部关闭

      • read阻塞

    2.写操作

    • 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)

    • 管道读端没有全部关闭:

      (1) 管道已满,write阻塞。

      (2) 管道未满,write将数据写入,并返回实际写入的字节数。

3、 管道缓冲区大小

可以使用ulimit –a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。通常为:

? pipe size (512 bytes, -p) 8

? 也可以使用fpathconf函数,借助参数 选项来查看。使用该宏应引入头文件<unistd.h>

? long fpathconf(int fd, int name); 成功:返回管道的大小 失败:-1,设置errno

设置非阻塞

在运行中,默认读写两端都是阻塞的,需要设置为非阻塞pipe

  • fcntl 变参函数:

    • 复制文件描述符:dup
    • 修改文件属性:open对应flag的属性
  • 设置方法:

    • 获取原来的flags

      int flags = fcntl(fd[0],F_GETFL)

    • 设置新的flags

      • flag|=O_NONBLOCK

      • flags = flags|O_NONBLOCK

      • fcntl(fd[0],F_SETFL,flags)

6、FIFO 有名管道/命名管道

1、特点

  • 有名管道
  • 在磁盘上有这样一个文件Is-| -> p
  • 伪文件,在磁盘大小永远为0
  • 在内核中有一个对应的缓冲区
  • 半双工的通信方式

2、使用场景

没有血缘关系的进程间通信

3、创建方式

mkfifo 管道名

库函数:int mkfifo(const char *pathname, mode_t mode); 成功:0; 失败:-1

一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。

4、进程间通信

mkfifo myfifo

两个不相干的进程 A(a,c),B(b,c)

a.c–>read

int fd = open("myfifo",O_RDONLY);

read(fd,buf,sizeof(buf));

close(fd);

b.c—>write

int fd 1= open(“myfifo”,O_WRDONLY);

write(fd1,"“hello”,11);

close(fd1);

7、内存映射区

存储映射I/O:Memory-mapped I/O,使一个磁盘文件与存储文件中的一个缓冲区进行映射,当从缓冲区取文件的时候,相当于读取文件中的相应字节。对应的,数据存入缓存区也会自动写入文件,在不使用read与write的情况下使用地址(指针)完成I/O操作。

mmap的示意图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I5qWvGBY-1627221175972)(D:\学习资料\C and C++\每日记录\Linux系统编程.assets\image-20210721104412055.png)]

mmap函数

void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);

返回:成功:返回创建的映射区首地址;

失败:MAP_FAILED宏

参数:

? addr: 建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL

? length: 欲创建映射区的大小

? prot: 映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE

? flags: 标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)

  • MAP_SHARED: 会将映射区所做的操作反映到物理设备(磁盘)上。

  • MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。

    fd: 用来建立映射区的文件描述符

? offset: 映射文件的偏移(4k的整数倍),一般设置为0

munmap函数

同malloc函数申请内存空间类似的,mmap建立的映射区在使用结束后也应调用类似free的函数来释放,mmap对应的释放函数就是munmap;

int munmap(void *addr, size_t length);

成功:0; 失败:-1

示例,读取hello.c 文件,输出文件的内容,并释放映射区

#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/mman.h>
#include<fcntl.h>

int main(int argc, const char* argv[]) {
        //打开一个文件
        int fd;
        fd = open("hello.c", O_RDWR);
    	//获取文件大小,也就是创建的映射区的大小
        int len = lseek(fd, 0, SEEK_END);


        //创建内存映射区
        void* ptr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
        if (ptr == MAP_FAILED) {
               perror("mmap error");
               exit(1);
        }
        printf("%s\n", (char*)ptr);
        //释放映射区
        munmap(ptr, len);
C
        return 0;
}

成功读取文件中的内容并显示

在这里插入图片描述

思考题
  1. 如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否
    能够成功?

    不能,不可以对其做操作

  2. 如果open时O_ RDONLY, mmap时prot参数指定
    PROT_ READ| PROT WRITE会怎样?

    会出现权限错误,前后权限一定要对应;

    mmap调用失败

  3. 如果文件偏移量为1000会怎样?

    不行,应该为4k(4096)的整数倍,会直接跳到4k的偏移量处;

  4. 如果不检测mmap的返回值会怎样?

    出现错误后,不知道是什么错误

  5. mmap什么情况下会调用失败?

    • 第二个参数len=0;

    • 第三个参数必须指定PROT_READ权限,open的打开的权限fd必须大于等于mmap制定的权限

    • 偏移量:必须为4096的整数倍

  6. 可以open的时候O_ CREAT一个新文件来创建映射区吗?

    可以,但是需要做文件拓展

    • lseek
    • truncate(path,length)
  7. mmap后关闭文件描述符,对mmap映射有没有影响?

    无影响

  8. 对ptr越界操作会怎样?

    段错误

使用mmap进行有血缘关系进程交流
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/mman.h>
#include<fcntl.h>
#include<string.h>

int main(int argc, const char* argv[]) {
        //打开一个文件
        int fd;
        fd = open("hello.c", O_RDWR);
        int len = lseek(fd, 0, SEEK_END);


        //创建内存映射区
        void* ptr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
        if (ptr == MAP_FAILED) {
              perror("mmap error");
              exit(1);
        }

        pid_t pid = fork();
        if(pid == -1){
                perror("fork error");
                exit(1);
        }

        if(pid>0){
                //write data
                strcpy((char*)ptr,"you are my son?");

                wait(NULL);
        }else if(pid ==0){
                printf("%s\n",(char*)ptr);
        }

        printf("%s\n", (char*)ptr);
        //释放映射区
        munmap(ptr, len);

        return 0;
}
     

在这里插入图片描述

在这里插入图片描述

可以看到,在父进程中,将其写入文件中,子进程打印对应的输入。

创建匿名映射区

//创建匿名内存映射区
void* ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON , -1, 0);

在第四个位置上加一个MAP_ANON

无血缘关系进程之间通信

mmap作为内核借助文件创建了一个映射区,多个进程之间利用该映射区完成数据传递,由于内核空间多进程共享,因此无血缘关系的进程间也可以使用,mmap来完成通信。设置相应的标志位flags为MAP_SHARED就可以

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-07-26 12:24:56  更:2021-07-26 12:26:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 17:20:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码