鸿蒙内核源码分析(管道文件篇) | 如何降低数据流动成本 | 百篇博客分析OpenHarmony源码 | v70.01
百篇博客系列篇.本篇为:
v70.xx 鸿蒙内核源码分析(管道文件篇) | 如何降低数据流动成本 | 51.c.h.o
文件系统相关篇为:
- v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 51.c.h.o
- v63.xx 鸿蒙内核源码分析(文件系统篇) | 用图书管理说文件系统 | 51.c.h.o
- v64.xx 鸿蒙内核源码分析(索引节点篇) | 谁是文件系统最重要的概念 | 51.c.h.o
- v65.xx 鸿蒙内核源码分析(挂载目录篇) | 为何文件系统需要挂载 | 51.c.h.o
- v66.xx 鸿蒙内核源码分析(根文件系统) | 先挂到
/
上的文件系统 | 51.c.h.o - v67.xx 鸿蒙内核源码分析(字符设备篇) | 字节为单位读写的设备 | 51.c.h.o
- v68.xx 鸿蒙内核源码分析(VFS篇) | 文件系统和谐共处的基础 | 51.c.h.o
- v69.xx 鸿蒙内核源码分析(文件句柄篇) | 深挖应用操作文件的细节 | 51.c.h.o
- v70.xx 鸿蒙内核源码分析(管道文件篇) | 如何降低数据流动成本 | 51.c.h.o
什么是管道
- 管道 | pipes 最早最清晰的陈述来源于
McIlroy
由1964
年写的一份内部文件.这份文件提出像花园的水管那样把程序连接在一起.文档全文内容如下:Summary--what's most important.
To put my strongest concerns into a nutshell:
1. We should have some ways of coupling programs like
garden hose--screw in another segment when it becomes when
it becomes necessary to massage data in another way.
This is the way of IO also.
2. Our loader should be able to do link-loading and
controlled establishment.
3. Our library filing scheme should allow for rather
general indexing, responsibility, generations, data path
switching.
4. It should be possible to get private system components
(all routines are system components) for buggering around with. M. D. McIlroy
October 11, 1964
Unix
的缔造者肯.汤普森
只花了一个小时就在操作系统中实现了管道的系统调用.他自己说这是简直小菜一碟,因为I/O的重定向机制是管道的实现基础,但效果确是很震撼.管道的本质是I/O
的重定向,是对数据的不断编辑,不断流动,只有这样的数据才有价值.- 在文件概念篇中提到过,
Unix
"一切皆文件"的说法是源于输入输出的共性,只要涵盖这两个特性都可以也应当被抽象成文件统一管理和流动. 拿跟城市的发展来举例,越是人口流动和资金流动频繁的城市一定是越发达的城市.这个道理请仔细品,城市的规划应该让流动的成本变低,时间变短,而不是到处查身份证,查户口本.对内核设计者来说也是一样,能让数据流动的成本变得极为简单,方便的系统也一定是好的架构,Unix
能做到多年强盛不衰其中一个重要原因是它仅用一个|
符号实现了文件之间的流动性问题.这是一种伟大的创举,必须用专门的章篇对其大书特书.
管道符号 |
管道符号是两个命令之间的一道竖杠 |
,简单而优雅,例如,ls
用于显示某个目录中文件,wc
用于统计行数.
ls | wc
则代表统计某个目录下的文件数量
再看个复杂的:
$ < colors.txt sort | uniq -c | sort -r | head -3 > favcolors.txt
colors.txt
为原始的文件内容,输出给sort
处理sort
对colors.txt
内容进行顺序编辑后输出给uniq
处理uniq
对 内容进行去重编辑后输出给sort -r
处理sort -r
对内容进行倒序编辑后输出给head -3
处理head -3
对 内容进行取前三编辑后输出到favcolors.txt
文件保存.- 最后
cat favcolors.txt
查看结果$ cat favcolors.txt
4 red
3 blue
2 green
经典管道案例
以下是linux
官方对管道的经典案例. 查看 pipe
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int pipefd[2];
pid_t cpid;
char buf;
if (argc != 2) {
fprintf(stderr, "Usage: %s <string>\n", argv[0]);
exit(EXIT_FAILURE);
}
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) { /* Child reads from pipe */
close(pipefd[1]); /* Close unused write end */
while (read(pipefd[0], &buf, 1) > 0)
write(STDOUT_FILENO, &buf, 1);
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
} else { /* Parent writes argv[1] to pipe */
close(pipefd[0]); /* Close unused read end */
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]); /* Reader will see EOF */
wait(NULL); /* Wait for child */
exit(EXIT_SUCCESS);
}
}
解读
pipe(pipefd)
为系统调用,申请了两个文件句柄,并对这两个文件进行了管道绑定. 在鸿蒙管道的系统调用为SysPipe
,具体实现往下看.main
进程fork()
了一个子进程,具体的fork
过程请前往 v45.xx (Fork篇) | 一次调用,两次返回 翻看.子进程将复制父进程的文件资源.所以子进程cpid
也拥有了pipefd
两个句柄,背后的含义就是可以去操作pipefd
对应的文件if (cpid == 0)
代表的是子进程的返回,close(pipefd[1])
关闭了pipefd[1]
文件句柄,因为程序设计成子进程负责读文件操作,它并不需要操作pipefd[1]
while (read(pipefd[0], &buf, 1)
子进程不断的读取文件pipefd[0]
的内容.- 按理说能不断的读取
pipefd[0]
数据说明有进程在不断的往pipefd[0]
中写入数据.但管道的思想是往pipefd[1]
中写入数据,数据却能跑到pipefd[0]
中.
(cpid > 0)
也就是代码中的} else {
代表的是父进程main
的返回.close(pipefd[0])
关闭了pipefd[0]
文件句柄,因为程序设计成父进程负责写文件,它并不需要操作pipefd[0]
write(pipefd[1], argv[1], strlen(argv[1]))
父进程往pipefd[1]
中写入数据.数据将会出现在pipefd[0]
中供子进程读取.
鸿蒙实现
管道的实现函数级调用关系如下:
SysPipe //系统调用
AllocProcessFd //分配两个进程描述符
pipe //底层管道的真正实现
pipe_allocate //分配管道
"/dev/pipe%d" //生成创建管道文件路径,用于创建两个系统文件句柄
pipecommon_allocdev //分配管道共用的空间
register_driver //注册管道设备驱动程序
open //打开两个系统文件句柄
fs_getfilep //获取两个系统文件句柄的实体对象 `file`
AssociateSystemFd //进程和系统文件句柄的绑定
其中最关键的是pipe
,它才是真正实现管道思想的落地代码,代码稍微有点多,但看明白了这个函数就彻底明白了管道是怎么回事了,看之前先建议看文件系统相关篇幅,有了铺垫再看代码和解读就很容易明白.
int pipe(int fd[2])
{
struct pipe_dev_s *dev = NULL;
char devname[16];
int pipeno;
int errcode;
int ret;
struct file *filep = NULL;
size_t bufsize = 1024;
/* Get exclusive access to the pipe allocation data */
ret = sem_wait(&g_pipesem);
if (ret < 0)
{
errcode = -ret;
goto errout;
}
/* Allocate a minor number for the pipe device */
pipeno = pipe_allocate();
if (pipeno < 0)
{
(void)sem_post(&g_pipesem);
errcode = -pipeno;
goto errout;
}
/* Create a pathname to the pipe device */
snprintf_s(devname, sizeof(devname), sizeof(devname) - 1, "/dev/pipe%d", pipeno);
/* No.. Allocate and initialize a new device structure instance */
dev = pipecommon_allocdev(bufsize, devname);
if (!dev)
{
(void)sem_post(&g_pipesem);
errcode = ENOMEM;
goto errout_with_pipe;
}
dev->d_pipeno = pipeno;
/* Check if the pipe device has already been created */
if ((g_pipecreated & (1 << pipeno)) == 0)
{
/* Register the pipe device */
ret = register_driver(devname, &pipe_fops, 0660, (void *)dev);
if (ret != 0)
{
(void)sem_post(&g_pipesem);
errcode = -ret;
goto errout_with_dev;
}
/* Remember that we created this device */
g_pipecreated |= (1 << pipeno);
}
else
{
UpdateDev(dev);
}
(void)sem_post(&g_pipesem);
/* Get a write file descriptor */
fd[1] = open(devname, O_WRONLY);
if (fd[1] < 0)
{
errcode = -fd[1];
goto errout_with_driver;
}
/* Get a read file descriptor */
fd[0] = open(devname, O_RDONLY);
if (fd[0] < 0)
{
errcode = -fd[0];
goto errout_with_wrfd;
}
ret = fs_getfilep(fd[0], &filep);
filep->ops = &pipe_fops;
ret = fs_getfilep(fd[1], &filep);
filep->ops = &pipe_fops;
return OK;
errout_with_wrfd:
close(fd[1]);
errout_with_driver:
unregister_driver(devname);
errout_with_dev:
if (dev)
{
pipecommon_freedev(dev);
}
errout_with_pipe:
pipe_free(pipeno);
errout:
set_errno(errcode);
return VFS_ERROR;
}
解读
- 在鸿蒙管道多少也是有限制的,也由位图来管理,最大支持32个,用一个32位的变量
g_pipeset
就够了,位图如何管理请自行翻看位图管理篇.要用就必须申请,由pipe_allocate
负责.#define MAX_PIPES 32 //最大支持管道数
static sem_t g_pipesem = {NULL};
static uint32_t g_pipeset = 0; //管道位图管理器
static uint32_t g_pipecreated = 0; static inline int pipe_allocate(void)
{
int pipeno;
int ret = -ENFILE; for (pipeno = 0; pipeno < MAX_PIPES; pipeno++)
{
if ((g_pipeset & (1 << pipeno)) == 0)
{
g_pipeset |= (1 << pipeno);
ret = pipeno;
break;
}
}
return ret;
} - 管道对外表面上看似对两个文件的操作,其实是对一块内存的读写操作.操作内存就需要申请内存块,鸿蒙默认用了
1024 | 1K
内存,操作文件就需要文件路径/dev/pipe%d
.size_t bufsize = 1024;
snprintf_s(devname, sizeof(devname), sizeof(devname) - 1, "/dev/pipe%d", pipeno);
dev = pipecommon_allocdev(bufsize, devname);
- 紧接着就是要提供操作文件
/dev/pipe%d
的VFS
,即注册文件系统的驱动程序,上层的读写操作,到了底层真正的读写是由pipecommon_read
和pipecommon_write
落地.ret = register_driver(devname, &pipe_fops, 0660, (void *)dev);
static const struct file_operations_vfs pipe_fops =
{
.open = pipecommon_open, /* open */
.close = pipe_close, /* close */
.read = pipecommon_read, /* read */
.write = pipecommon_write, /* write */
.seek = NULL, /* seek */
.ioctl = NULL, /* ioctl */
.mmap = pipe_map, /* mmap */
.poll = pipecommon_poll, /* poll */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
.unlink = pipe_unlink, /* unlink */
#endif
};
pipecommon_read
代码有点多,此处不放出来,代码中加了很多的信号量,目的就是确保对这块共享内存能正常操作. - 要操作两个文件句柄就必须都要打开文件,只不过打开方式一个是读,一个是写,
pipe
默认是对fd[1]
为写入,fd[0]
为读取,这里可翻回去看下经典管道案例的读取过程.fd[1] = open(devname, O_WRONLY);
fd[0] = open(devname, O_RDONLY);
- 最后绑定
file
的文件接口操作,在文件句柄篇中已详细说明,应用程序操作的是fd | 文件句柄
,到了内核是需要通过fd
找到file
,再找到file->ops
才能真正的操作文件.ret = fs_getfilep(fd[0], &filep);
filep->ops = &pipe_fops; ret = fs_getfilep(fd[1], &filep);
filep->ops = &pipe_fops;
鸿蒙内核源码分析.总目录
v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o
百万汉字注解.百篇博客分析
百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee| github| csdn| coding >
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto| csdn| harmony| osc >
关注不迷路.代码即人生
QQ群:790015635 | 入群密码: 666
原创不易,欢迎转载,但请注明出处.
鸿蒙内核源码分析(管道文件篇) | 如何降低数据流动成本 | 百篇博客分析OpenHarmony源码 | v70.01的更多相关文章
- 鸿蒙源码分析系列(总目录) | 百万汉字注解 百篇博客分析 | 深入挖透OpenHarmony源码 | v8.23
百篇博客系列篇.本篇为: v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o 百篇博客.往期回顾 在给OpenHarmony内核源码加注过程中,整理出以下 ...
- 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 百篇博客分析OpenHarmony源码 | v62.01
百篇博客系列篇.本篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 51.c.h.o 本篇开始说文件系统,它是内核五大模块之一,甚至有Linux的设计哲学是" ...
- v72.01 鸿蒙内核源码分析(Shell解析) | 应用窥伺内核的窗口 | 百篇博客分析OpenHarmony源码
子曰:"苟正其身矣,于从政乎何有?不能正其身,如正人何?" <论语>:子路篇 百篇博客系列篇.本篇为: v72.xx 鸿蒙内核源码分析(Shell解析篇) | 应用窥视 ...
- v75.01 鸿蒙内核源码分析(远程登录篇) | 内核如何接待远方的客人 | 百篇博客分析OpenHarmony源码
子曰:"不学礼,无以立 ; 不学诗,无以言 " <论语>:季氏篇 百篇博客分析.本篇为: (远程登录篇) | 内核如何接待远方的客人 设备驱动相关篇为: v67.03 ...
- v76.01 鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式 | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(共享内存篇) | 进程间最快通讯方式 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) | 同样 ...
- v79.01 鸿蒙内核源码分析(用户态锁篇) | 如何使用快锁Futex(上) | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(用户态锁篇) | 如何使用快锁Futex(上) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...
- v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...
- v80.01 鸿蒙内核源码分析(内核态锁篇) | 如何实现快锁Futex(下) | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(内核态锁篇) | 如何实现快锁Futex(下) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...
- 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 | 百篇博客分析OpenHarmony源码 | v71.01
子曰:"我非生而知之者,好古,敏以求之者也." <论语>:述而篇 百篇博客系列篇.本篇为: v71.xx 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 ...
随机推荐
- pycharm使用Djiago创建第一个web项目
安装PyCharm专业版(注意社区版创建Djiago需要配置,比较麻烦) 创建Djiago项目点上 1.Inherit glocal site-packages(不然pycharm不去下载Djiago ...
- 基于typescript编写vue的ts文件语法模板
1 <template> 2 <div> 3 <input v-model="msg"> 4 <p>prop: {{ propMes ...
- GPRS RTU设备OPC Server接口C# 实现
通过本OPC Server程序接口可为用户提供以OPC标准接口访问远程GPRS/3G/以太网 RTU设备实时数据的方式.从而方便实现GPRS/3G/以太网 RTU设备与组态软件或DCS系统的对接.本程 ...
- .NET Core:在ASP.NET Core WebApi中使用Cookie
一.Cookie的作用 Cookie通常用来存储有关用户信息的一条数据,可以用来标识登录用户,Cookie存储在客户端的浏览器上.在大多数浏览器中,每个Cookie都存储为一个小文件.Cookie表示 ...
- windows上解决git每次重复输入账号密码
win7电脑: 1.在 C:\Users\Administrator 下 编辑 .gitconfig文件 2.在原有内容下添加一行(此行作用为自动保存,保存修改后再使用一次GIT,输入账号密码后下次即 ...
- C++继承体系中的内存分段
---------------综述与目录-------------- 讨论这个问题之前我们先明确类的结构,一个类的大概组成,下面的很多分类名词都是我个人杜撰,为的就是让读者看懂能够区分,下面分别分类: ...
- Html 之自动高度 auto 和 100%高度
HTML 高度 下面示例 设置为 Auto 和 100% <!DOCTYPE html> <html lang="en"> <head> < ...
- Saruman's Army
直线上有N个点. 点i的位置是Xi.从这N个点中选择若干个,给它们加上标记. 对每一个点,其距离为R以内的区域里必须有带有标记的点(自己本身带有标记的点, 可以认为与其距离为 0 的地方有一个带有标记 ...
- web.xml中自定义Listener
Listener可以监听容器中某一执行动作,并根据其要求做出相应的响应. 常用的Web事件的监听接口如下: ServletContextListener:用于监听Web的启动及关闭 ServletCo ...
- ES6扩展——箭头函数
1.箭头函数 在es6中,单一参数的单行箭头函数语法结构可以总结如下: const 函数名 = 传入的参数 => 函数返回的内容,因此针对于 const pop = arr => arr. ...