【操作系统】虚拟化CPU、Memory,共享文件

    科技2023-09-13  111

    几个概念

    CPU、虚拟CPU进程内存、虚拟地址空间

    物理的CPU被OS虚拟成了多个虚拟的CPU,这些虚拟CPU分别运行各自的程序,这些正在运行的程序被称为进程。物理内存被OS虚拟成了多个虚拟地址空间,每个进程都有独立的、自己的地址空间,程序的指令和数据都在地址空间中磁盘被OS虚拟化为文件系统,文件是被多个程序共享的,它并不是多个虚拟的磁盘,不过也不是无条件共享,涉及到例如互斥共享等多个问题,以后再谈。

    1 Virtualizing the CPU

    我们在Linux系统上运行C语言程序,体会一下虚拟化的意义。

    Windows对多用户的支持不是很好,相关的系统API可能也没有,推荐适用Linux或Unix系统。

    // cpu.c

    #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <assert.h> int main(int argc,int *argv[]){ if(argc != 2){ fprintf(stderr,"usage:cpu <string>\n"); exit(1); } char *str = argv[1]; for(int i = 0;i < 4;i++){ sleep(1); printf("%s\n",str); } return 0; }

    这个程序很容易,需要运行的时候输入一个参数,比如一个字符,值得解释的是sleep(1),也就是让程序暂停1秒,这非常重要,这意味着物理的CPU在这1s时间可以不用执行该进程,转而执行其他进程。

    注意,虚拟后的CPU,最终仍然要在真实物理CPU来执行,要想让每个进程都得到执行,那就应该以合理的方式让他们切换执行。

    我们先运行一个进程试试看,输入命令./cpu A1: 打印了4个A1,并且是每隔1s打印一个,这与我们的预期相符。

    接下来,我们同时运行多个进程试试看,输入命令./cpu A1 & ./cpu B2 & ./cpu C3 & 按照直观的理解,不应该是

    A1 A1 A1 A1 B2 B2 B2 B2 C3 C3 C3 C3

    不应该是这样吗?但是看起来这3个进程并不是顺序执行的,而是并发执行的,也就是它们趁着其他进程在sleep的时候,抢占了CPU去执行自己了(注意,我们假设计算机只有1个CPU,而且是单核的)。

    这样一来,就出现了图中的乱序了。

    我们也能充分的感受到,不要让物理CPU闲着的重要理念,同时我们也能想象到,多个进程同时执行,就会涉及到更多的问题,如果是之前的顺序执行,我们只需要进程1执行,其他等待–>进程1执行完成,进程2执行,其他等待–>进程2执行完成,进程3执行–>进程3执行完成。

    也就是说,我们只需要等着一个程序执行完,再执行其他程序,这样很简单,但是效率非常低,比如,如果正在执行的程序不使用CPU,去“sleep”了,或者去找I/O设备“玩”了,CPU就只能呆着,其他程序也不能进来执行,CPU利用率很低。

    为了避免这种问题,现代OS都采用了类似多道批处理的技术,正在执行的程序不执行时,其他程序会进入CPU执行,而不会允许CPU空闲,要榨干CPU!

    就如上面的程序,当一个进程sleep的时候,其他进程就会进入CPU执行,但是,具体如何执行,取决于OS的调度程序,取决于OS设计的策略,所以目前我们还不能得知它具体是如何运作的(也许你可以查看Linux内核,不过如果你有此能力,就不会看见这篇文章了)。

    1.1 补充:实例中的C语言知识

    以下请自学

    1.1.1 main函数参数,argc和argv

    1.1.2 fprintf()

    1.1.3 sleep()

    2 Virtualizing Memory

    我们先上代码

    // mem.c

    #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int argc,int *argv[]){ int *p = malloc(sizeof(int)); // assert(p != NULL); printf("(%d) memory address of p: %08x\n",getpid(),(unsigned)p); *p = 0; for(int i = 0;i < 4;i++){ sleep(1); *p = *p + 1; printf("(%d) p: %d\n",getpid(),*p); } return 0; }

    运行程序./mem 运行多个进程:./mem & ./mem & ./mem & 这里,我们依然能够看到的是虚拟化CPU,不过,虚拟化内存在哪里呢?目前还看不出来,因为Linux默认是启动地址空间随机化的,这样会让系统更安全,不易受到攻击,不过为了展现虚拟化内存,我们应该关掉它。

    输入命令sysctl -w kernel.randomize_va_space=0,再输入./mem & ./mem & ./mem &

    我们可以看到,三个进程居然地址完全一样!按理说,1个地址只能对应1个进程,所以,你就能体会到虚拟地址空间的含义了,这并不是真实的物理地址,它会通过某种机制,映射到真实物理地址去。

    3 Sharing Disk Information

    还记得我们刚才的两个程序吗?他们同时启动了多个进程,并且,这几个进程是同一个程序,也就是说,同一个存储在磁盘的文件,被多次读取到了内存,这也就意味着,磁盘信息是可以被同时多次读取的,我们也可以说,这几个进程共享了一个磁盘文件。


    思考:为什么内存和CPU要虚拟化为多个,而磁盘却是共享的?

    进程是运行中的程序,它是“活的;程序是静止在磁盘中的指令和数据,它是“死的”。

    对于正在运行的进程来说,我们需要为其独立地分配一整套生态系统,保证它正常执行,并且每个程序运行时候的结果可能不同,所以,就虚拟地提供了CPU和地址空间,让它们是相互独立的;而对于静止的指令和数据来说,完全没有必要虚拟成多份,那反而是浪费空间,当然这是针对读取而言,写入还需要视情况,不过整体来说,读取信息是及其场景的,将磁盘设为共享也是合理的。


    另外要谈的是,磁盘文件必须通过软件和硬件协作的方式,使其持久地保存,而不是很快就消失了,或者被其他数据覆盖掉了。

    4 Concurrency

    虚拟化对应的是进程,而并发对应的不仅仅是OS的进程,在OS之上的应用程序,也存在并发的问题,他就是多线程编程;虚拟化让一个CPU能并发地执行多个进程,而一个进程,也能并发地执行多个线程。

    你一定知道多线程编程,是的,就是那个,我们现在重新审视一下它。

    // threads.c

    #include <stdio.h> #include <stdlib.h> #include <pthread.h> volatile int counter = 0; int loops; void *worker(void *arg) { int i; for (i = 0; i < loops; i++) { counter++; } return NULL; } int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "usage: threads <value>\n"); exit(1); } loops = atoi(argv[1]); pthread_t p1, p2; printf("Initial value : %d\n", counter); pthread_create(&p1, NULL, worker, NULL); pthread_create(&p2, NULL, worker, NULL); pthread_join(p1, NULL); pthread_join(p2, NULL); printf("Final value : %d\n", counter); return 0; }

    我们进行编译gcc threads.c -o threads -lpthread,注意,<pthread.h>不是Linux默认的库,编译链接需要加上参数-lpthread,也就是需要链接额外的Import Library:libpthread.a。

    我们进行测试: 对于输入的参数N,输出结果应该是2N(先知道事实,看不懂多线程程序没有关系),但是最后两个,当参数足够大,比如5亿的时候,结果就诡异了。

    这是由于计数器的值的更新不是原子操作,他需要:

    内存–>寄存器寄存器递增寄存器–>内存

    3个步骤,但是,这几个步骤可能被其他操作打断,这就造成了结果的诡异。关于原子操作以后再说。

    5 小结

    我们谈了几件事儿

    物理CPU – 虚拟化CPU – 多进程并发物理内存 – 虚拟地址空间 – 进程独立地址空间磁盘(持久性) – 文件系统 – 共享磁盘信息OS之上的并发:单个进程中的多线程

    版权声明

    本文是读书笔记,来自于书籍《Operating System:Three Easy Pieces》

    Processed: 0.022, SQL: 8