Operating system interfaces

xv6 介绍的第一章。

Operating system interfaces

这是课本的第一章就是总体概述一下操作系统的作用以及操作系统一些名词的解释,例如进程,文件描述符等。

用我自己的话来总结操作系统:操作系统就是对底层硬件的抽象,封装底层硬件的功能,为软件运行提供简单有效的运行环境。计算机说到本质其实还是 我们用一些01的序列来操控硬件设备实现我们需要的功能,但是不可能让所有的程序员都直接去操纵01吧,那这样门槛实在是太高。所以先驱们就将硬件能做的事情,封装好只提供一个简单快捷的接口来上层调用,这样我们只需要关注我们的业务功能,而不需要关注底层硬件是怎么工作的。这样的思想贯穿整个计算机学科。 其实在生活中也会有很多这样的思想。比如平时做菜煮汤的过程中,如果有一次性的综合调味包,只需要直接一次性放进去煮一段时间,就能做成凑活能吃的东西,但是如果从加盐 加酱油等等步骤开始,就需要我们操心每一步的时机以及量的多少,这样就会让新手玩家容易出错(当然从原始的步骤开始能够打造更具特色的口味,操作系统也一样,只不过不像吃饭,需要os有特色功能的情况太少,又或者说现有功能已经足够满足绝大部分人的需求了)。

Proceses and memory

常见的一句话是 进程是操作系统 分配资源的最小单位。学习了xv6源码后能能对这句话有更深的理解(遗憾的是xv6的进程中不支持多线程,因此在xv6中进程也成了cpu调度的最小单位了)。一个进程有自己的 代码区,数据区,堆区,和栈区。并且每一个进程都有自己的状态(运行,可以运行,或者睡眠),操作系统内核进行着对这些进程的调度,我们使用者是完全看不见的。进程切换这个非常常见,但是其实这个动作是一个抽象的词,是由多个动作共同完成后,组成的这个动作。(例如,保存寄存器,切换页表等)这些后面都会有深刻的源码解析。

操作系统提供fork()接口来实现进程的拷贝,也就是创建新的进程,通过exit()来销毁进程。这两个系统调用就不在多说了。后面会直接看源码的。

exec()系统调用会直接将本来的进程被替换为另一个进程。这个接口也是shell程序运行的基础函数。 shell运行新线程的基本方法就是先fork()一个进程,然后调用exec()来替换复制的进程。当然最开始看这的时候很容易有疑问,例如:好不容易刚创建的一个新进程,我们又马上的把它替换掉,有毛病吧,这里面肯定有优化的空间。这就涉及到我们后面的页表的作用的了,也就是所谓的copy on write(cow),后面会有lab专门做这个,非常有趣。此外课本还提到了一个问题:一般来说fork与exec很多情况下都是一起使用的,那么os为什么不直接提供一个接口同时实现这两个功能呢? 其实对于这类问题的答案,思想是普遍的即:合在一起意味着简单,高效;分开意味着灵活,操作性高。(又回到了我们os上面提到的思想了)。所以这里没有合在一起是显然是灵活性更加重要。下一节有具体解释。

I/O and File descriptor

linux中常见的一句话是 一切皆文件。 如何理解这句话呢? 我的理解就是linux中对于所有的东西都抽象的非常好,普通的软件应用时文件这个比较好理解。但是硬件如何是文件呢?显然linux吧硬件抽象成文件,我们通过直接操控这个文件来实现对硬件的操作,当然底层还是os的封装。 (os真是太好用了)

xv6就是超级mini版的linux,一切也是文件。而我们他通过文件描述符来直接管理文件。这个文件描述符是一个非负整数(一般可以用int或者uint 类型)。通过操纵这个数字。我们可以直接的对底层的相应的文件进行操作。简单的来说就是底层的文件系统中会有一个打开的文件的数组,而我们操作的这个数字就是这个数组的下标,内核通过下标来找到对应的打开的文件。对于每一个进程来说都默认有三个打开文件描述符。0表示标准输出,1表示标准输入,2表示标准错误。这里的默认实现其实是对于xv6的第一个进程来说,他在初始化的时候就打开了这三个文件描述符,所以后面的fork也都是默认打开的(exec替换进程,但是保留的打开的文件描述符)。这里回答一下上面一小节的问题。fork与exec的分开实现,意味着我们可以在这两个实现的中间完成我们需要的I/O重定向的工作,也就是打开或者关闭文件描述符,来实现我们的需求。

这里有一个有有意思的点是:两个进程对同一个文件进行操作,最终的结果是如何呢,例如同时写入数据,是追加呢还是覆盖呢?

这里我们也可以提前深入一点底层,在底层中对于打开的文件都有一个 file数据机结构表示,其中会有一个off表示偏移, 每一个进程都有用一个 file*的数组指向对应的file文件。如果我们的两个进程是通过fork来实现,并且在fork前就打开了某个文件,那么这两个进程的打开的文件都会指向相同的一个file文件,因此这样两个进程对于文件的操作就是追加。反之 如果我们是在fork后各自打开的相同的文件描述符,那么这两个进程的file* 会指向各自不同的file。从而写入的内容是覆盖。代码例子如下:

​
int main() {
    int fd = open("data.txt", O_RDWR);        
    int pid =  fork();
    if(pid == 0) {
        //子进程
         write(fd, "hello ", 6);
         exit(0);
    }else{
        //父进程
         wait(NULL);
         write(fd, "world!\n", 7);
    }
    return 0;
}  //该程序会在data.txt中生成 hello world!
   
int main() {      
    int pid =  fork();
    int fd = open("data.txt", O_RDWR);
    if(pid == 0) {
        //子进程
         write(fd, "hello ", 6);
         exit(0);
    }else{
        //父进程
         wait(NULL);
         write(fd, "world!\n", 7);
    }
    return 0;
} //该程序会在data.txt中生成 world! 由于是在创建进程后再分别open,虽然打开的是相同的文件,但是不共享偏移!
​

Pipes

管道是内核中的一个缓冲区,作为一对文件描述符,一个可以写,另一个可以读。可以作为进程间的一种通信方式。比如说通过fork()来创建的父子进程就可以通过这个pipe来实现通信。命令中 |也是用了管道。

File system

这个后面有一章单独说。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值