惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

文章列表

游戏玩后感:ReLief:献给亲爱的你 我的周边(谷子)分享 游戏玩后感:Kanon 简谱:致真实的你 《Rust中常见的有关生命周期的误解》学习笔记 简谱:StarMap 简谱:かく咲きたらばいと恋ひめやも 简谱:东风 简谱:无法诉说的思念 简谱:Girlish 游戏玩后感:时钟机关的Layline 简谱:风之琶音 简谱:星空的记忆 简谱:因为遇见了你 简谱:月童 番茄简谱脚本转调器 游戏玩后感:青空下的约定:Refine 游戏玩后感:在这苍穹展翅 书籍读后感:控制论与科学方法论 游戏玩后感:恋爱表达式 游戏玩后感:樱之诗 MLIR-tutorial学习笔记 游戏玩后感:潜伏之赤途 游戏玩后感:纯爱咖啡厅:帕露菲重制版 游戏玩后感:智以泪聚 游戏玩后感:初雪樱 游戏玩后感:告别回忆:从今以后 游戏玩后感:梦灯花 游戏玩后感:金辉恋曲四重奏 游戏玩后感:五彩斑斓的世界 昇腾310P使用记录 游戏玩后感:AIR 游戏玩后感:弹丸论破 游戏玩后感:流景之海的艾佩莉亚 Xilinx_HLS上板过程记录 游戏玩后感:告别回忆2 游戏玩后感:恋爱绮谭 Faiss和Rapidsai_Raft使用记录 游戏玩后感:近月少女的礼仪 游戏玩后感:樱色之云,绯色之恋 游戏玩后感:幸运草的约定 游戏玩后感:星之梦、候鸟和丸子与银河龙 游戏玩后感:白色相簿2 Windows上使用VTune分析PyTorchExtension调用的Cpp程序 SpinalHDL上板过程记录 游戏玩后感:仰望夜空的星辰 最简单的算卦方法之一:梅花易数法 游戏玩后感:苍之彼方的四重奏 krkr引擎解包工具介绍 自定义CUDA实现PyTorch算子的四种简单方法 游戏玩后感:星空的记忆 游戏玩后感:9nine 游戏玩后感:AtriMyDearMoments 游戏玩后感:极限脱出 游戏玩后感:魔女的夜宴 SSH实现多跳代理 动漫观后感:向山进发 flv重封装H264、AAC流 动漫观后感:夏日重现 CSP模板 游戏玩后感:海沙风云 动漫观后感:灵能百分百 游戏玩后感:交响乐之雨 游戏玩后感:爱上火车LastRun 游戏玩后感:LittleBustersEX 游戏玩后感:SummerPockets 游戏玩后感:逆转裁判 Ultra96V2开发板简单使用 SpinalWorkshop实验笔记(三) SpinalWorkshop实验笔记(二) SpinalWorkshop实验笔记(一) PYNQ开发板上使用USB声卡+OSS兼容层播放音频 TestOS移植K210开发板 rCore-Tutorial-Book-v3学习笔记(七) 动漫观后感:凉宫春日的忧郁 rCore-Tutorial-Book-v3学习笔记(♭七) rCore-Tutorial-Book-v3学习笔记(五) rCore-Tutorial-Book-v3学习笔记(四) 动漫观后感:吹响吧上低音号 使用OpenCV对图片进行特征点检测和匹配 git基本操作
rCore-Tutorial-Book-v3学习笔记(六)
VnYzm · 2021-04-10 · via

概述

第六部分是实现基本的文件数据结构、标准输入输出文件和管道文件。相对来讲比较简单,不过因为每个进程的文件描述符表需要用一个既能动态扩增又能直接索引的数据结构,所以我顺便自己实现了一个简单的vector,并利用相同的思想实现了一个队列,使用非常简单,所以又把queue.h换掉了。

内容

首先是vector和queue,我采用了倍增扩容的方法,即容量满了以后扩大一倍。为了保证空间足够,需要将其包含的数据类型的大小作为属性存入结构体,开辟空间和复制操作的时候都需要乘以该属性,这里以vector和queue的push函数为例:

void vector_push(struct vector *v, void *d) {
    if (v->size == v->capacity) {
        v->capacity <<= 1;
        char *t = bd_malloc(v->capacity * v->dsize);
        memcpy(t, v->buffer, v->size * v->dsize);
        bd_free(v->buffer); v->buffer = t;
    }
    memcpy(v->buffer + (v->size++) * v->dsize, d, v->dsize);
}
void queue_push(struct queue *q, void *d) {
    if (q->size == q->capacity) {
        q->capacity <<= 1;
        char *t = bd_malloc(q->capacity * q->dsize);
        memcpy(t, q->buffer, q->tail * q->dsize);
        memcpy(t + (q->capacity - (q->size - q->front)) * q->dsize,
                q->buffer + q->front * q->dsize,
                (q->size - q->front) * q->dsize);
        q->front = q->capacity - (q->size - q->front);
        bd_free(q->buffer); q->buffer = t;
    }
    memcpy(q->buffer + q->tail * q->dsize, d, q->dsize);
    q->tail = (q->tail + 1) % q->capacity; q->size++;
}

vector的操作很直观,queue要麻烦一些,为了节省空间,queue用的是循环队列,当队列满时,头索引和尾索引必然相等,因为尾索引是向右增加,头索引是像左减少的,所以扩容的时候需要将原缓冲区最左侧到尾索引的数据复制到新缓冲区的最左侧,将原缓冲区头索引到最右侧的数据复制到新缓冲区的最右侧,这时候就得计算好复制到新缓冲区的起始位置。

然后是文件描述符的设计,rCore很好地利用了rust语言的面向对象特性,让File对象“继承”(我不确定rust语言里这个行为叫什么,大概类似于java里继承抽象类)管道、磁盘文件、设备等对象,这样File对象就只要实现一些最基本的方法和属性,比如定义文件写和文件读函数的指针,具体的属性和方法,如管道释放方法、管道读写权限、管道对应缓冲区、磁盘文件索引之类就通过“继承”赋予给File对象;xv6就比较朴素,在文件描述符里对应了多种多样的属性,但是没有定义函数指针,而是由统一的读写函数通过文件描述符里的文件类型属性判断。

我则综合了两者,给文件描述符定义了包括读、写、释放、复制多个函数的指针,这样在读、写、释放的系统调用中就不用判断文件类型,而是调用对应的函数指针即可,同时也在文件描述符中额外定义了一个void类型的指针,用来指向这个文件“绑定”的数据,比如是管道文件,这个指针就指向一个管道。在调用文件处理函数指针的时候把文件指针传进去,由不同的函数来对这个“绑定”指针进行不同的解释,算是一种伪泛型吧。

这里还有一点要注意的,就是文件的引用计数问题。这个问题对于rCore和xv6的意义不同。对于rCore来说,这个计数不是针对于单个的文件描述符,而是针对于文件描述符所产生的跨进程资源。比如管道文件,虽然每个进程各自持有,释放也各自释放就行,但是管道包含一个用来传输数据的缓冲队列,这个缓冲队列就是跨进程资源。这就需要引用计数,对于每个缓冲队列记录自己有多少个“管口”,当所有“管口”被关闭时,即没有管道引用它时,就要释放这个缓冲队列。这里rust又拿下一城:拥有内置了引用计数功能的智能指针,不需要显式地处理引用计数问题。再看看xv6,xv6是父子进程共享一个文件描述符,所以引用计数是文件描述符的一个属性,每次进程fork都会增加一次引用,释放文件会减少一次引用,当引用为0时将文件描述符及其对应的资源释放。

我在文件描述符和进程的关系上遵循的是rCore,即每个进程持有一个独立的文件描述符,而把引用计数属性放在需要被释放的结构体中,比如这里就给管道添加了引用计数属性,同时定义了管道释放和复制的方法对其指向的缓冲区引用计数进行修改,这一步就比较像xv6,然后把这两个函数填入文件描述符的函数指针中,由fork函数和文件关闭函数调用。

这里给出管道相关的创建、读写、复制、释放函数:

usize pipe_read(File *self, char *buffer, usize len) {
    Pipe *p = (Pipe *)self->bind;
    for (int i = 0; i < len; i++) {
        while (queue_empty(&p->q)) {
            if (p->wrefcnt == 0) {
                buffer[i] = '\0'; return i;
            }
            suspend_current_and_run_next();
        }
        buffer[i] = *(char *)queue_front(&p->q); queue_pop(&p->q);
    }
    return len;
}
usize pipe_write(File *self, char *buffer, usize len) {
    Pipe *p = (Pipe *)self->bind;
    for (usize i = 0; i < len; i++) queue_push(&p->q, buffer + i);
    return len;
}
void pipe_copy(File *self) {
    Pipe *p = (Pipe *)self->bind;
    if (self->read == illegal_rw) p->wrefcnt++; p->refcnt++;
}
void pipe_close(File *self) {
    Pipe *p = (Pipe *)self->bind;
    if (self->read == illegal_rw) p->wrefcnt--;
    self->occupied = 0; p->refcnt--;
    if (p->refcnt == 0) {
        queue_free(&p->q); bd_free(p);
    }
}
void make_pipe(usize *p) {
    Pipe* t = bd_malloc(sizeof(Pipe));
    queue_new(&t->q, 1); t->refcnt = 2; t->wrefcnt = 1;
    File *f = alloc_fd(p); f->bind = (void *)t;
    f->read = pipe_read; f->write = illegal_rw;
    f->copy = pipe_copy; f->close = pipe_close;
    f = alloc_fd(p + 1); f->bind = (void *)t;
    f->read = illegal_rw; f->write = pipe_write;
    f->copy = pipe_copy; f->close = pipe_close;
} 

我这里Pipe结构体和rCore不太一样,rCore里Pipe对象的意思是“管口”,有额外的PipeRingBuffer对象表示管道本身。我这里Pipe就是管道本身,文件描述符中bind属性指向它,所以文件描述符本身就充当“管口”了。管道中还有一个wrefcnt属性表示写引用计数,在读函数中如果判断写引用计数为0,说明管道中不会再有新数据了,则直接返回已经读取的全部数据。

在复制和释放管道文件的时候,判断释放应该修改写引用计数的方法是判断该文件的读函数是否指向illegal_rw,这个函数是一个直接返回错误的函数,用来填充该文件不支持的操作对应函数指针,如果管道文件的读函数指向illegal_rw,说明这个管道文件不支持读取,是写管口,此时就应该修改写引用计数。