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系统编程-》IPC之管道+面试题【第2天:7325字】 -> 正文阅读

[系统运维]嵌入式Linux系统编程-》IPC之管道+面试题【第2天:7325字】

在这里插入图片描述


😋😋😋😋😋😋😋😋😋😋😋😋😋
Linux专栏开源,点点我,一起走进开源世界
😋😋😋😋😋😋😋😋😋😋😋😋😋


第一话-》匿名管道pipe

【1】进程间通信(IPC)概览

Linux/Unix系统中,进程间通信方式(Inter-Process Comunication)通常有如下若干中方式:

管道

匿名管道 pipe适用于亲缘关系进程间的、一对一的通信
具名管道 fifo适用于任何进程间的一对一、多对一的通信
套接字 socket适用于跨网络的进程间通信
信号异步通信方式

system-V IPC对象

共享内存效率最高的通信方式
消息队列相当于带标签的增强版管道
信号量组也称为信号灯,用来协调进程间或线程间的执行进度

POSIX信号量

POSIX匿名信号量适用于多线程,参数简单,接口明晰,童叟无欺
POSIX具名信号量适用于多进程,参数简单,接口明晰,老少咸宜

这些通信机制统称IPC,它们各有特色,各有适用的场合。


【2】 匿名管道PIPE

2.1 基本逻辑

不管是匿名管道还是具名管道,在Linux系统下都属于文件的范畴,区别是匿名管道没有名称因此无法使用open创建或打开,事实上匿名管道有自己独特的创建接口,但其读写方式与普通的文件一样,支持read()/write()操作

管道文件事实上还包括网络编程中的核心概念套接字,所谓的管道指的是这些文件不能进行“定位”,只能顺序对其读写数据,就像一根水管,拧开水龙头不断读取,就可以源源不断读到水管中的数据,但如果没有水出来那只能继续等待,不能试图“跳过”部分文件去读写水管的中间地带,这是管道的最基本的特性。
在这里插入图片描述

2.2 函数接口
创建匿名管道的函数接口非常简单,如下所示:

#include <unistd.h>

int pipe( int fd[2] );

注意1:
由于匿名管道拥有两个文件描述符一个专用于读fd[0],一个专用于写fd[1],因此上述接口需要传递一个至少包含两个整型元素的数组过去,用来存放这两个特定的描述符。

注意2:
匿名管道描述符,只能通过继承的方式传递给后代进程,因此只能用于亲缘进程间的通信,由于没有文件名,其他非亲缘进程无法获取匿名管道的描述符。

注意3: 不能有多个进程同时对匿名管道进行写操作,否则数据有可能被覆盖。

总结一句话,匿名管道适用于一对一的、具有亲缘关系的进程间的通信。

下面以父子进程使用匿名管道通信的例子对PIPE的使用加以说明,假设父进程先创建一条匿名管道,然后产生一个子进程,此时子进程自然继承了这条管道的读写端描述符,进而它们就可以通信了。
在这里插入图片描述

示例代码如下:

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

int main(void)
{
    // 创建匿名管道
	int fd[2];
	pipe(fd);

    // 子进程
	if(fork() == 0)
	{
        // 向父进程打招呼
		char *msg = "hello parent!";
		write(fd[1], msg, strlen(msg));

		exit(0);
	}

    // 父进程
	else
	{
	    char buf[50];
        bzero(buf, 50);

        // 静静地等待子进程的消息 
		read(fd[0], buf, 50);

		printf("来自子进程: %s\n", buf);
		exit(0);
	}
}

注意,匿名管道的读写端是严格区分的,任何不规范的操作都是不允许的,其结果都是不确定的。

另外还应该注意到,一般而言,不需要用到的文件描述符都最好及时关闭,避免不必要的副作用或浪费系统资源。例如上述程序中,子进程只用到了管道的写端,因此它的fd[0]可以也应该要关闭,相反父进程只用到了管道的读端,因此它的fd[1]可以也应该关闭。

代码可以改成:

int main(void)
{
    // 创建匿名管道
    int fd[2];
    pipe(fd);

    // 子进程
    if(fork() == 0)
    {
        // 关掉不必要的读端
        close(fd[0]);

        ...
    }

    // 父进程
    else
    {
        // 关掉不必要的写端
        close(fd[1]);

        ...
    }
}

2.3 管道的读写特性

当我们对一个管道文件(包括匿名管道、具名管道和网络socket)进行读写操作时,我们需要知道将会发生什么,比如读一个空管道会怎么样?对一个缓冲区已满的管道执行写入操作会怎么样等等,可以对这些读写操作做一个统一的整理。

术语约定:

读者: 对管道拥有读权限的进程
写者: 对管道拥有写权限的进程

注意,所谓的读者、写者不是只正在读或者正在写的进程,而是只要拥有读写权限就称为管道的读者写者,比如如下进程关闭了匿名管道的读端,因此它只能称为匿名管道的写者:

// 创建匿名管道
int fd[2];
pipe(fd);

// 关闭读端,剩下写端
close(fd[0]);
//又如下面这个进程,使用读写权限打开了具名管道,因此该进程既是读者也是写者:

int fd = open("fifo", O_RDWR);

下面是读写特性对照表:
在这里插入图片描述

2.4 管道的阻塞特性

仔细看管道读写特性的表会发现,当试图读取一个空管道,或者试图写入一个缓冲区已满的管道时读写操作默认会进入所谓“阻塞(se)”的状态。所谓的阻塞实际上就是系统将该进程挂起,等待资源就绪再继续调度的一种状态,这种阻塞的状态有利于系统中别的进程可高效地使用闲置CPU资源,提高系统的吞吐量。

对于阻塞而言,有如下特性需要记忆:

  • 普通文件,默认是非阻塞的,且不可修改。
  • 管道文件,默认是阻塞的,可修改

以下是设置管道文件阻塞特性的代码:

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

int main()
{
    // 管道默认为阻塞
    int fd[2];
    pipe(fd);

    // 1,将管道设置为非阻塞
    long flag = fcntl(fd[0], F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(fd[0], F_SETFL, flag);

    int n;
    char buf[20];

    // 此处,读不到数据将立即返回
    n = read(fd[0], buf, 20);
    if(n < 0)
        perror("read failed");

    // 2,将管道重新设置为阻塞
    flag = fcntl(fd[0], F_GETFL);
    flag &= ~O_NONBLOCK;
    fcntl(fd[0], F_SETFL, flag);

    // 此处,读不到数据将持续等待
    n = read(fd[0], buf, 20);
    if(n < 0)
        perror("read failed");

    return 0;
}

注意: 管道打开时,必须同时有读者和写者,否则 open 也会阻塞。


第二话-》具名管道FIFO

【3】具名管道FIFO概述

具名管道是跟匿名管道相对而言的,从外在形态上来看,具名管道更接近普通文件,有文件名、可以open打开、支持read()/write()等读写操作

具名管道通常又被称为FIFO(First In First Out),这其实所所有管道的基本特性,那就是放入的数据都是按顺序被读出,即所谓先进先出的逻辑。
在这里插入图片描述

当然,管道并不是普通文件,具名管道特性:

与PIPE一样不支持定位操作lseek() 与PIPE一样秉持相同的管道读写特性
使用专门的接口来创建:mkfifo()(匿名管道是pipe()) 在文件系统中有对应节点,支持使用 open() 打开管道(匿名管道不具备)
支持多路同时写入(匿名管道不具备)


【4】 函数接口

以下是创建具名管道的函数接口

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

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

注意1:pathname即具名管道的名称,若是新建的管道文件,则需保证创建路径位于Linux系统内,尤其是虚拟机中操作的时候,不可将管道文件创建在共享文件夹中,因为共享文件夹是windows系统,不支持管道文件。

注意2:mode是文件权限模式,例如0666,注意权限须为八进制,且实际管道的权限还受系统 umask 的影响。

以下是两个程序通过管道互相通信的示例代码:

// 进程A,创建管道并向管道写入字符串"data from FIFO."
int main()
{
    // 创建具名管道
    mkfifo("/tmp/fifo", 0666);

    // 向管道写入数据
    int fd = open("/tmp/fifo", O_RDWR);
    char *msg = "data from FIFO";
    write(fd, msg, strlen(msg));

    close(fd);
    return 0;
}
// 进程B,从管道读出数据
int main()
{
    // 从管道读出数据
    int fd = open("/tmp/fifo", O_RDWR);

    char buf[50];
    bzero(buf, 50);
    read(fd, buf, 50);

    printf("%s\n", buf);

    close(fd);
    return 0;
}

注意3:具名管道一旦没有任何读者和写者,系统判定管道处于空闲状态,会释放管道中的所有数据。


第三话-》面试题

问:老师,什么是无名管道和有名管道?

答:这些都是翻译的问题,PIPE一般被称为匿名管道,也称为无名管道;FIFO一般被称为具名管道,也称为有名管道、命名管道等。

问:老师,什么时候用PIPE?什么时候用FIFO?

答:亲缘进程间的少量数据交互,并且是一对一的情况下,使用匿名管道即可。如果不是亲缘进程,那就无法使用匿名管道。如果是多对一通信,比如经典的日志类系统,多个进程会同时向同一个管道写入数据,由于匿名管道不保证写入的原子性,也就是同时写入的数据可能会互相践踏破坏,因此这种情况也不能使用匿名管道,而可以使用具名管道。还有就是如果传输的数据量比较大,并且比较在意时效性,那么这两种管道可能都不合适,也许可以考虑本地域套接字或共享内存。

编程实现下述命令的执行效果,查看系统进程列表中的指定进程信息:

gec@ubuntu:~$ ps ajx | grep 'xxx' --color
253
gec@ubuntu:~$ 

解答:

这道题结合了进程基本API和匿名管道、文件描述符重载等知识,首先需要创建两个进程,并用管道将它们联系起来,再分别去加载ps和grep这两个程序,利用文件描述符重载技术,将ps和grep的输入和输出用管道联通起来。

以下是示例代码:

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc, char **argv)
{
    int fd[2];
    pipe(fd);

    if(argc != 2)
    {
        printf("用法: %s 要搜索的进程名\n", argv[0]);
        exit(0);
    }

    if(fork() == 0)
    {
        dup2(fd[1], STDOUT_FILENO);

	// 子进程执行 "ps ajx"
        execlp("ps", "ps", "ajx", NULL);
    }

    else
    {
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);

	// 父进程执行 "grep"
        execlp("grep", "grep", argv[1], "--color", NULL);
    }

    exit(0);
}

(exec函数族、匿名管道、文件描述符重定向)

【1】编程实现下述命令的执行效果,计算指定目录下的文件个数:

gec@ubuntu:~$ ls /etc/ | wc -w
253
gec@ubuntu:~$ 

解答: 这道题跟本节随堂练习一样,主要要注意在负责读取数据的进程中,要正确关闭不需要的管道写端,否则会导致自身的循环IO阻塞等待而无法退出。

以下是示例代码:

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc, char **argv)
{
    int fd[2];
    pipe(fd);

    if(argc != 2)
    {
        printf("用法: %s 指定文件夹\n", argv[0]);
        exit(0);
    }

    if(fork() == 0)
    {
        dup2(fd[1], STDOUT_FILENO);

	// 子进程执行 "ls"
        execlp("ls", "ls", argv[1], NULL);
    }

    else
    {
	// 关闭写端,否则父进程进入自身循环等待导致无法退出
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);

	// 父进程执行 "wc"
        execlp("wc", "wc", "-w", NULL);
    }

    exit(0);
}

(精灵进程、有名管道FIFO)

【2】编程实现两个程序:一个服务器server,一个客户机client。
要求:

服务器创建并监视有名管道FIFO,一旦发现有数据则将其保存到一个指定的地方。 客户机每隔一段时间产生一个子进程。
客户机的这些子进程将当前系统时间和自身的PID写入有名管道FIFO就退出。

解答:

// 服务端程序示例代码:
int main(int argc, char **argv)
{
	mkfifo("/tmp/fifo", 0666);
	int fifofd = open("/tmp/fifo", O_RDWR);
	int logfd  = open("/tmp/log.txt", O_WRONLY|O_CREAT|O_APPEND, 0777);

	char buf[1024];
	while(1)
	{
		bzero(buf, 1024);
		read(fifofd, buf, 1024);
		write(logfd, buf, strlen(buf));
	}
	return 0;
}
//客户端代码:
int main(int argc, char **argv)
{
	mkfifo("/tmp/fifo", 0777);
	int fd = open("/tmp/fifo", O_WRONLY);

	char buf[1024];
	time_t t;
	while(1)
	{
		bzero(buf, 1024);

		time(&t);
		snprintf(buf, 1024, "[%-6d] %s",
			getpid(), ctime(&t));

		write(fd, buf, strlen(buf));
		sleep(1);
	}

	return 0;
}

(管道读写特性、阻塞与非阻塞)

【3】根据管道的读写特性,编写一个程序,测试你系统管道文件的缓冲区大小。

解答:

根据管道读写特性可知,当管道的缓冲区满了再继续写入数据时,默认会阻塞,这将无法让程序正常汇报缓冲区的尺寸,因此只需在写入测试数据前将管道设置为非阻塞即可。

以下以匿名管道为例,展示示例代码:

int main()
{
    // 管道默认为阻塞
    int fd[2];
    pipe(fd);

    // 将管道写端设置为非阻塞
    long flag = fcntl(fd[1], F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(fd[1], F_SETFL, flag);

    // 持续不断写入数据,直到报错为止
    int total = 0;
    while(1)
    {
        if(write(fd[1], "x", 1) < 0)
        {
            perror("写入失败");
            break;
        }
        total++;
    }

    printf("缓冲区大小:%d个字节\n", total);
}

在这里插入图片描述
#端午趣味征文赛–用代码过端午#

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-06-06 17:33:28  更:2022-06-06 17:35:21 
 
开发: 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年5日历 -2024/5/18 21:39:26-

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