- 博客(46)
- 收藏
- 关注
原创 Lwip源码剖析 -- ip4_input() 和 ip4_output()
【代码】Lwip源码剖析 -- ip4_input() 和 ip4_output()
2024-05-29 11:09:41 346
原创 xv6源码分析 017
现在假设没有logging,我们的文件系统调用是直接写到磁盘上对应的块上的,这样的io我们称之为随机io,不仅效率低下,而且如果在操作执行到一半的时候突然发生意外,系统崩溃了或者断电了,现在文件系统是不完整,因为操作系统并不知道故障发生之前的操作执行到哪里,也不知道是否全部的操作都已经完成了,更不知道那些操作需要被撤销。:如果当前的文件的日志正在提交(文件的事务正在提交),那么这个操作将进行睡眠,直到事务提交成功,因为xv6中没有数据库系统那样复杂的并发控制机制,所以只支持一个文件一次事务提交。
2024-05-07 23:33:14 642
原创 xv6源码分析 016
我们可以看到,xv6中是为整个buffer cache维护了一把大锁,导致每一个只能有一个进程访问buffer cache,在后期的lab中,就有一个lab需要我们涉及一个细粒度锁的buffer cache来优化,并支持并发访问。之前我们是自顶向下进行分析的(从文件描述符层开始,一直到物理存储层),现在看代码的话我们就是自底向上进行的,因为我们上层使用的接口都实现在下面的层次,如果直接从顶层开始讲的话,很容易就一头雾水的。下面就是初始化LRU链表,这里我们需要注意的是,buffer cache只是用。
2024-05-04 23:26:48 673
原创 xv6源码分析 015
特别的,在数据库系统中有一种不同于B+树的树叫LSM-tree(log structure - merge tree)日志结构合并树,这个并不是日志,但是它采用了日志的一种特性,append-write,这种特性大大地减少了磁盘写入时的开销,但是同时也会增加了读操作的开销,因为在进行读操作是,需要底层网上遍历整个树直至找到对应的块,还会进行数据合并(因为可以会有一些数据被反复修改在tree中存在多个无效的副本)。日志是一种特殊的文件,它写入的方式是append,就是在文件的末尾进行拓展。
2024-05-03 23:28:20 686
原创 xv6源码解析 014
如果我们是在内核中处理其他的事情(我暂时想不出有什么其他的事情),此时陷入处理的向量也是指向这个函数,所以如果有一个无论是什么情况引起的trap,我们都会直接跳转到这个函数中进行处理。现在我们就安全的返回了用户空间了,站在用户进程的视角来看,用户进程并没有察觉时间的流逝(一个比喻,对于(分时操作系统的)用户进程来说,消耗了时间片才被视为是时间的流逝),相当于时间暂停了一下下,当返回时,又重新开始了。这是一个不会返回的函数,正如我们上篇说到的,在trap的整个过程链中的函数都是不会返回的。
2024-05-01 23:17:50 50
原创 xv6源码分析 013
我们需要了解用用户态陷入内核态并从内核态返回用户态,从内核态进行陷入并返回的整个过程,以及会调用那些函数,陷入之前的进程的上下文如何被保存,保存在哪里,陷入过程中需要改变那些寄存器的值,从陷入中恢复之后需要如何恢复这些寄存器的值,等等。在内核中,ecall相当于一条没用的指令,所以我们需要跳过这条指令,指向下一条需要执行的指令,注意这两条指令不一定是在连续的物理内存中的。函数的入口处,我们现在来执行这个函数,这个函数的主要的功能是路由(routine):判断陷入的原因,并执行相应的处理程序。
2024-04-30 00:13:44 891
原创 xv6源码分析 012
它被定义在kernel.ld文件中,这个文件是一个ARM架构的链接脚本(linker script),用来指导链接器如何布局目标文件中的各个段(section)。bss(block started by symbol)段是程序在内存中占用的一个特定的区域,主要用来存放未初始化的全局变量和静态变量。是块链表,每一个节点代表一个内存块(memory block),固定4096个字节的大小。xv6的内存配置器是基于单向链表的,这是最简单实现方式。并不是库函数中的memset,是自己实现的。我们需要注意,这里的。
2024-04-26 23:45:50 982
原创 xv6源码分析 011
为什么要先检查进程的进程锁是否已经被获取呢?我们上一集看过sleep的代码,在sleep中,进程首先获取进程锁,为了防止唤醒信号的丢失;这里检查进程锁是否被获取,看起来有点多余哈哈哈,而且是因为如果没有检测结果为false的化就会panic,这个在正常的系统中肯定是不被允许的。也可以这样理解,kill的作用具有延迟性,在进程由于中断或者系统调用陷入内核时,kill才起作用。这个函数用于唤醒所有在这个channel结构上睡眠的进程,类似于我们c++中的。OK,proc.c和proc.h的讲解到此结束啦!
2024-04-24 23:19:48 238
原创 xv6内核剖析 010
调度器执行完这个函数,并不会返回,因为这个函数底层是汇编代码,它改变了一系列寄存器的值:加载了被调度进程的上下文,并且将原来的调度器的上下文保存cpu中的context属性中以备下一次调度,我们可以理解为调度器在调用完swtch()之后就被暂停了,然后cpu通过ra寄存器返回了被调度进程上下文,那什么时候调度器的上下文才会被加载呢?取代原本的context,这样就切换到了调度函数中了,就能够进行调度,而原来的进程就作为一个就绪状态调度进程等待cpu的调度。的时候提到,在这个cpu的结构体中有一个。
2024-04-24 00:01:16 586
原创 xv6源码剖析 009
从代码中,我们知道,当调用fork的时候,内核会完全复制父进程的页表,进程地址空间,trapframe page和相应的文件描述符,等等,唯一不同的就是,内核会直接将父进程的返回值返回(在代码中),而对于子进程则是将返回值保存在一个寄存器中;其实本质上,它们都是相同的,只是在代码中的体现有所不同,因为这个调用是由父进程引起的。子进程和父进程是相互独立的,它们并不共享相同的资源,这是由操作系统的隔离机制决定的,但是线程不一样,线程是运行在进程中的,而且一个进程中的不同进程是共享一部分进程的内存的;
2024-04-21 00:06:28 664
原创 xv6源码剖析 008
两者都是通过调用内联的汇编函数来打开或者禁止设备中断(device interrupts),因为xv6在自旋锁创建的临界区之间是不允许中断的,因为这有可能会产生死锁;进程收养、确保资源及时回收以及维持孤儿进程的正常运行或终止流程,有效地管理孤儿进程,避免了因父进程异常导致的子进程管理问题和系统资源浪费。一样,也是在内核的main函数中调用的,它初始化内核的第一个用户进程,这个进程作为这个操作系统进程树(process tree)的根节点(root)。的,因为进程也是来自于内核的,但是更都更了,先写完。
2024-04-21 00:05:03 1099
原创 xv6源码剖析 008
两者都是通过调用内联的汇编函数来打开或者禁止设备中断(device interrupts),因为xv6在自旋锁创建的临界区之间是不允许中断的,因为这有可能会产生死锁;进程收养、确保资源及时回收以及维持孤儿进程的正常运行或终止流程,有效地管理孤儿进程,避免了因父进程异常导致的子进程管理问题和系统资源浪费。一样,也是在内核的main函数中调用的,它初始化内核的第一个用户进程,这个进程作为这个操作系统进程树(process tree)的根节点(root)。的,因为进程也是来自于内核的,但是更都更了,先写完。
2024-04-20 00:02:02 569
原创 xv6源码分析 007
用于实现trap机制,即在用户进程陷入内核态的时候,内核需要将该cpu上的该用户态进程的寄存器的状态保存在trapframe中,这样在恢复trap,返回用户空间的时候我们才能够继续正常地执行我们的指令。:每一个进程都有一个context,一般在没有进程或线程调度的时候,context保存的是这个进程的内核线程的上下文。在xv6中,线程调度需要先切换到内核的线程,然后这个内核线程的上下文和cpu中保存的上下文(cpu调度线程的上下文)交换,来回到到调度器线程中,在来选择下一个处于就绪状态的进程。
2024-04-18 23:59:00 770
原创 xv6源码分析 006
能够减少MMU的工作,因为在只有一个内核页表的情况下,这个内核页表是和用户进程的页表分离的,所以在内核页表中并不包含用户进程的地址空间的映射,这就意味着,在用户态有效的地址(va),在内核页表上是无效的,所以内核需要将这些地址转换成物理地址,才能够在内核页表上使用。,页表项,进程的进程地址空间在xv6中是由一个一个固定的4096个字节的虚拟页面(virtual page)来组成的,但是每个虚拟页的实际物理页其实不同的,所以说,一个进程的虚拟地址空间是连续的,但是这个进程的物理地址空间不一定是连续的。
2024-04-17 23:59:39 1000
原创 xv6 源码分析 005
所以,我们需要一个我们信得过的应用程序来维护我们的硬件,保护我们的硬件不会被非法访问或者非法使用,并且这个应用程序还需要承担监督运行在这个计算机上的其他的我们信不过的应用程序的行为,限制它们能够访问的范围,并且将多个应用程序对硬件的访问进行管理(让他们不要一窝蜂的乱来),,,等等功能,所以,这就有了我们的操作系统,也被称为内核(kernel)。我们可以假设一下,如果没有操作系统,那么我们的应用程序是直接跑在硬件之上的,我放个图吧。(兄弟们到点了,讲了个大概,后面的我们以后再细讲了,睡了家人们)。
2024-04-12 00:12:55 746
原创 xv6源码剖析 004
可以看到其实在下面的内存中我们是有足够的空间来容纳两个新的数据块的,但是,因为这几个空闲内存块并不连续,导致了malloc返回一个空指针,在实际情况中我们可能也会发现,我们的内存利用率并不高,但是内存分配总是失败,有可能就是这个原因。(在xv6中一个页固定大小为4096,当然我们实现内存池的时候当然可以根据我们自己的需要将内存划分为多种不同的大小的块,就是将xv6的这个配置器看成我们内存池的一个子集)。这一步也很简单,找到第一个合适的块,如果块的大小刚刚好,直接全部给,否则就进行切割,
2024-04-11 00:13:51 1089
原创 xv6源码分析 003
大致就是这个情况,好像并不会调用其他的函数,可能是我的测试命令太简单了,但是大致的调用流程和我上面给出的流程图是一样的,这种设计思路我们可以学习学习,其实我们也能够用函数指针的方法来实现,可能是由于我们命令有时候会比较复杂,比如当我们调用。今天是2024/4/9,发现我也看不懂了,不死磕了大佬们,简单做了个实验,发现我还是太年轻了,,而且我也大致的给出了第二个函数的实现的思路,现在就来正式的看看吧。这种复合命令的时候,就需要这种过滤的思维,来逐个解析命令。的字符的位置,并将对应的指针返回。
2024-04-09 00:20:28 826
原创 xv6源码分析 002
我们可以将xv6的shell看成是一个代理(proxy),我们将我们需要执行的命令交给这个帮我们代理的对象(shell),然后它再帮我们真正去做这件事情,然后将处理的结果返回给我们。这一层代理替我们与操作系统的用户态进行进行交互,我们我们不需要将写各种的函数来将我们的命令传递到内核,等内核处理完之后,再写各种的函数获取内核的处理的结果。了解一下内存池的实现,对我们水平的提高也是有帮助的。中,大家可以去看看,里面的函数的都很极致的,因为都是大佬写的,而且在面试的时候,面试佬可能会让你实现一些api。
2024-04-07 00:16:22 851
原创 xv6源码分析 001
是一些用户程序,也就是我们平时在shell上面执行的命令,每执行一个命令就会创建一个新的用户进程来执行这个命令。:主要是输出一段汇编指令,并通过重定向输入到指定的文件中,下面我们就来看看这段汇编。很明显,这是系统调用的trap过程,但是这个是什么语言我还不清楚,我们先看看xv6这个项目的基本结构(只看代码部分)ok,今晚就先到这里了,我们明晚再继续吧。ok,今晚就先到这里了,我们明晚再继续吧。.long 0 # 一个空指针。的过程,我就直接在源码上注释了。
2024-04-06 00:06:08 873
原创 内存映射在coping gc中的应用
我们就正式开始看源码,听说这个小demo有很多不足的地方。但是笔者对gc确实不太了解,真的看不来哈哈。主要关于一个使用mmap对baker gc算法进行优化的小demo。先让大家看看baker算法对coping gc优化的精髓部分。
2024-03-30 16:14:55 315
原创 muduo异步日志
陈硕老师的muduo网络库的异步日志的实现,今晚有点晚了,我明晚再把这个异步日志抽出来,作为一个独立的日志库。这个CountDownLatch有点像信号量,但是又只有down操作,上网查了以下类似的,作用有点像屏障。
2024-03-21 00:10:26 385
原创 xv6内核源码解析trap.c
为了方便我把整个实验代码都搬过去了,在windows下来看会方便一点,主要是招函数比较方便。要是觉得麻烦直接看博客也行,不过我觉得对着来看效果会好一点。基于实验4,我们先熟悉一下。
2024-03-17 22:35:49 479
原创 C++对象模型剖析(十六)一一执行期语义学(二)
这个问题书上并没有给出答案,这里说一下我的观点:我觉得编译器应该会在new的时候做一个标记,在下次new的时候检查这个标记判断是否需要调用析构函数,比如。还有一个点需要我们注意:**一般来说C++的内存管理会将内存分为五个区:常量区,静态(全局)区,栈区,堆区,自由存储区。的调用都必须传回一个独一无二的指针。也就是说我们可以这样,我写过的一个内存池就是用这种方法来分配定长的内存块来避免内存碎片化,虽然可能会有点内存的浪费。,哈哈,作者说这是引入new重载之后的已给最隐晦的问题,大家重载的时候需要注意一下。
2024-03-14 11:03:46 321
原创 C++对象模型剖析(十五)一一执行期语义学(一)
的函数,产生出以class object构造而成的数组。在比较新的编译器中,则是提供两个,一个用来处理没有virtual base class的class,另一个用来处理内含virtual base class 的class。如上所示,每当一个对象被定义的时候,编译器就会显式插入一个该对象的构造函数,当该对象所在的作用为即将结束的时候,编译器就会自动为它安插一个析构函数。并没有定义一个constructor或者一个destructor,那么我们的编译器确实是直接为其分配一个足够的空间即可。
2024-03-13 10:55:48 942
原创 C++对象模型剖析(十四)一一构造、析构、拷贝语义学(四)
编译器默认是使用逐字进行拷贝的,但是上面的class中并没有指针或者应用这一类的东西,所以默认的拷贝行为也是足够的,而且编译器默认的行为不仅足够还有效率。当class内含member object 或者class的base class,它们中定义了destructor,那么编译器就会自动合成一个或者在我们定义的destructor中进行拓展,调用他们的destructor。否则,destructor被视为不需要,也就不需要被合成。的一个虚拟派生类,所以,在同一个继承体系中,不止一个类对。
2024-03-11 10:24:00 636
原创 C++对象模型剖析(十三)一一构造、析构、拷贝语义学(三)
书原话:以此为杠杆,我们可以产生更有效率的constructor。某些新进的编译器把每一个constructor分裂为二,一个针对完整的object,另一个针对subobject。“完整object”版无条件地调用 virtual base constructor,设定所有的vptrs等。“subobject”版则不调用virtual base constructor,也可能不设定vptrs等。vptr 初始化语义学 (the semantic of the vptr initialization)
2024-03-09 11:49:01 610 1
原创 C++对象模型剖析(十二)一一构造、析构、拷贝语义学(二)
编译器在优化状态下可能会把object的连续内容拷贝到另一个object身上,而不会实现一个精确地“以成员为基础”的赋值操作。一般而言,如果一个类中有比较多的函数返回一个 local class object,那么为这个类设计一个拷贝构造函数是有必要的。一般而言,编译器会对类的每一个构造函数进行扩张,扩张的程度由类的继承体系而定。能够避免一个多余的无效操作,也能避免在拷贝的时候将原来的对象释放,造成悬空引用。书上还给出一个用例,给我们展示了编译器对类的扩充的结果,我们先来看看把。
2024-03-08 11:15:04 1010 1
原创 C++对象模型剖析(十一)一一 构造、析构、拷贝语义学(一)
C++中保证在一个继承体系中的每一个 class object 的 destructor 都会被调用。并且编译器并会为一个类中的 pure virtual destructor 拓展其定义,所以,在设计一个类的时候,我们应该不要把virtual destructor 申明为 pure。我们再看一下。
2024-03-07 11:40:15 644
原创 C++对象模型剖析(十)一一Function语义学(三)
如果 vprt 被编译器放在 class 对象的开头处,这个字段就没有必要了,代价则是C对象兼容性降低,这些字段只在多重继承或虚拟继承的情况下才有必要,有许多编译器在自身内部根据不同的 classes 特性提供了多种指向 member functions 的指针的形式。换句话说,如果实际参数是一个常量表达式(constant expression),我们可以在替换之前先完成其求值操作,后继的inline替换,就可以把常量直接绑定上去,如果既不是常量表达式,也不是带有副作用的表达式,那么就直接替换。
2024-03-06 10:29:44 1109
原创 C++对象模型剖析(九)一一Function语义学(二)
这一小节还是将的有点抽象的,我在后面的多继承那里卡了挺久的,有一些涉及到内嵌汇编的小东西,哈哈哈。
2024-03-04 11:49:44 969
原创 001 mit6.s086实验解析
大佬们,如果看到不通顺或者有不对的地址可以联系我(第一次搞翻译),或者那些地方怎么翻译会更好。实验后续在开始,现在先做翻译,希望能够6级。
2024-03-03 19:48:01 270
原创 C++对象模型剖析(八)一一Function语义学(一)
Function语义学(一)C++支持三种类型的 function:nonstatic member functionstatic member functionvirtual member funcionMember 的各种调用方式Nonstatic Member Funcions非静态成员函数C++的设计准则之一就是:**nonstatic member funcion 至少必须和一般的 nonmember funcion 有相同的效率。**也就是说调用 nonstatic mem
2024-03-03 13:24:13 670
原创 C++对象模型剖析(七)一一Data语义学(三)
一般的实现方法是这样的:**Class 如果内含一个或多个 virtual base class subobjects,像 istream 那样,将被分割成两部分,一个不变区域和一个共享区域。这样我们就能够很清楚的知道多重继承和虚拟继承的区别,而且我们也能看出在多重继承的体系下,我们需要维护两个 ios base class object ,这就造成了空间和效率上的浪费,我们不仅要为这两个 ios object 分配空间,我们还要同步对他们的修改操作,来保证两个 object 是一样的。
2024-03-02 11:27:27 987
原创 C++对象模型剖析(六)一一Data语义学(二)
我的理解就是,一般来说,一个类的成员变量都是私有的,但是类会提供访问这些成员变量的接口,这些接口是公开的,但一个类的继承了另一个类之后,派生类虽然在实际上拥有了基类的成员变量,但是逻辑上,派生类对基类成员变量的访问时受到接口的限制的,同时,每一个接口的作用范围都被限定在了声明该接口的类中,并不能对其他类的成员变量进行修改,这应该差不多就是这个意思了。但是,现在 test1 和 test2 都是 8字节,拷贝的时候也都是拷贝 8 字节,这就导致了 test1 的数据会覆盖 test2 中的数据,导致原本的。
2024-03-01 12:35:38 1073 1
原创 C++对象模型剖析(五)一一 Data语义学(一)
这时候,我们不能够说 pt 必然指向哪一种 class type(因此,在编译期,编译器无法确定这个 member 的真正的偏移位置 offset,所以 pt 的存取操作就会延至执行期,经由一个额外的间接引导,才能解决。比如 vptr,但是不同的编译器会将 vptr 放在不同的位置上,有的会将 vptr 放在 class object 的头部,有的则会将 vptr 放在 class object 的尾部。如果取一个 static data member 的地址,会得到一个指向其数据类型的指针。
2024-03-01 11:49:48 1026 1
原创 C++对象模型剖析(四)一一构造函数语义学(三)
作者在书中给出的忠告:**请使用 “存在于 constructor 体内的一个 member”,而不要使用 “存在于 member initialization list 中的 member”,来为另一个 member 设定初值。这一期,我们讲 class 的 成员初值化列表(Member Initialization List)的使用以及可能存在的坑。本章对初值化列表的学习就到这里,后面还会在其他章节深入地讲解一下初值化列表,顺便分析他的底层的实现。但是初值化列表的初始化的顺序是 先 t1, 再 t2。
2024-02-28 10:32:11 407
原创 C++对象模型剖析(三)一一构造函数语义学(二)
先看第一个例子public:private:这种时候,就没有必要设置一个 copy cosntructor,这种情况下使用 bitwise copy 既高效又安全。
2024-02-28 10:30:07 997
原创 linux中的RCU机制
我忘了具体是什么意思,我们知道 page 是一个虚拟的概念,每个进程看到的都是虚拟内存,虚拟内存需要通过映射(mapping)到相应的物理内存上,kernel 代码中有对应的映射函数。**系统中数据读取操作远多于写操作,而RWlock(读写锁)机制在SMP对称多处理器环境下随着处理器增多性能会迅速下降,RCU机制改善了这种情况,**RCU机制的核心是写操作分为 “写” 和 “更新” 两步,**允许读操作在任何时候无阻碍访问,当系统有写操作是,更新动作一直延迟到对该数据的所有读操作完成为止。
2024-02-26 21:43:41 2330
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人