zoukankan      html  css  js  c++  java
  • 操作系统 | 概述

    程序运行时

    正在运行的程序就是一直在执行指令。处理器从内存中获取一条指令,对指令解码(弄清指令的语义),然后执行它。完成这条指令后处理器继续执行下一条指令,直到程序最终完成。操作系统负责让程序运行变得容易(可以运行多个程序),允许程序共享内存、和设备交互以及其他一些工作。

    《Operating Systems: Three Easy Pieces》 一书中将操作系统的特征分为虚拟化、并发和持久性3个部分。

    虚拟化&虚拟机

    虚拟化技术是操作系统将物理资源(如CPU、RAM、Disk)转变为更通用强大的虚拟形式。因此有时候操作系统也称为虚拟机(VMvare Work Station、JVM亦是虚拟机)。操作系统会提供系统调用,像标准库一样提供给应用程序用以运行、访问内存和I/O设备等。由于虚拟化技术使得许多程序共享CPU、RAM、Disk,因此操作系统是计算机系统资源的管理者,在资源分配上达到高效公平或是更多目标。

    接下来通过实际的代码看看相关虚拟技术的表现。

    虚拟化CPU

    先通过代码来模拟一下多进程的情形。

    common.h:

    #ifndef __common_h__
    #define __common_h__
    
    #include <sys/time.h>
    #include <sys/stat.h>
    #include <assert.h>
    
    double GetTime() { // 获取当前时间
    	struct timeval t;
    	int rc = gettimeofday(&t, NULL);
    	assert(rc == 0);
    	return (double) t.tv_sec + (double) t.tv_usec/1e6;
    }
    
    
    void Spin(int howlong) { // 等待howlong
    	double t = GetTime();
    	while ((GetTime() - t) < (double) howlong)
    		;
    }
    
    #endif
    
    

    cpu.c:

    间隔1s钟反复打印命令行传入的参数(字符串)。

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

    下面我们在Linux下运行这段程序,使用的shell为tcsh。

    这里的shell命令中,&用于创建后台进程。执行命令后,可以看到操作系统创建了PID(Process Identifier,进程标识符)为2086、2087、2088、2089的4个进程,虽然只有一个处理器,但4个进程似乎是在同时运行。这是因为操作系统提供了一种假象,即系统中拥有无限多的CPU,从而使得许多程序在宏观上看起来像是同时在运行,这就是虚拟化CPU

    1次运行4个进程会引发很多问题,这也是操作系统课程的聚焦。在这里的运行结果中我们可以看到,ABCD字符并没有按照顺序输出。程序的运行时间由操作系统的调度策略来决定。

    如果要将程序停止,使用kill命令通知操作系统即可。

    虚拟化内存

    下面尝试同时运行多个操作内存的程序,看看运行时内存中值的情况。

    mem.c:

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include "common.h"
    
    int main(int argc, char *argv[]) {
    	int *p = malloc(sizeof(int));
    	assert(p != NULL);
    	printf("(%d) memory address of p: %o8x
    ", 
               getpid(), (unsigned) p);  // 打印分配的内存地址
    	*p = 0;    // p指向地址存入0
    	while (1) {
    		Spin(1);
    		*p = *p + 1;
    		printf("(%d) p: %d
    ", getpid(), *p);
    	}
    
    	return 0;
    }
    
    

    在这里需要关闭ASLR(空间地址随机化布局),从而使程序分配到的内存段起始地址保持一致。

    setarch $(uname --machine) --addr-no-randomize /bin/bash
    

    运行多个内存分配程序,可以观察到每个程序都在相同的地址(555592a0)处分配了内存。对于该地址内存单元存储的值,每个程序都能够独立的更新而不受其他程序影响。在程序看来,内存是其私有的,而不是与其他正在运行的程序共享物理内存。

    这就是虚拟化内存技术,每个进程访问自己的私有虚拟地址空间(virtual address space),然后操作系统以某种方式将虚拟内存映射到机器的物理内存上。一个正在运行的程序中的内存引用不会影响其他进程(或操作系统本身)的地址空间。 对于正在运行的程序,它完全拥有自己的物理内存。

    并发(Concurrency)

    在上面关于虚拟化的实验中,可以看到操作系统同时处理很多事情,首先运行一个进程,然后再运行一个进程,以此类推。但是运行结果表明,这样做会导致一些深刻而有趣的问题。

    我们看看linux c下的一个多线程程序 threads.c:

    #include <stdio.h>
    #include <stdlib.h>
    #include "common.h"
    #include "common_threads.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 <loops>
    "); 
            exit(1); 
        } 
        loops = atoi(argv[1]);
        
        pthread_t p1, p2;
        printf("Initial value : %d
    ", counter);
    	// 创建两个线程,每个线程调用worker()增加共享计数器的值
        Pthread_create(&p1, NULL, worker, NULL); 
        Pthread_create(&p2, NULL, worker, NULL);
       
        Pthread_join(p1, NULL);
        Pthread_join(p2, NULL);
        printf("Final value   : %d
    ", counter);
        return 0;
    }
    

    这里要注意使用gcc编译时需要将线程库引入:

    这里的运行结果和预期一致。但是我们加大loops数值,异常就会逐步显现出来:

    这样不寻常的结果与指令如何执行有关。在面的程序中,关键部分是增加共享计数器的地方,它需要 3 条指令:一条将计数器的值从内存加载到寄存器,一条将其递增,操一条将其保存回内存。 但这3条指令并非原子方式执行(atomically,所有指令一次性执行),因此会导致异常。

    持久性

    RAM中的内存断电即丢失,因此需要硬件和软件持久的存储数据。Hard drive 和 SSD 是典型的用于存储长期保存的数据的IO设备。操作系统为CPU和内存提供了抽象,但是并不会为每个应用创建虚拟磁盘,并假设用户经常需要资源共享

    下面看一个学习C语言文件操作时常见的例子: io.c

    #include <stdio.h>
    #include <unistd.h>
    #include <assert.h>
    #include <fcntl.h>
    #include <sys/types.h>
    
    int main(int argc, char *argv[]) {
    	int fd = open("/tmp/file", 
                      O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
    	assert(fd > -1);
    	int rc = write(fd, "Hello World
    ", 13);
    	assert(rc == 13);
    	close(fd);
    	
    	return 0;
    }
    
    

    在这里程序向操作系统发出了open()write()close() 3个调用。这些系统调用(system call)被转到文件系统(file system)的操作系统部分,然后该系统处理这些请求。中间操作系统为了写入数据究竟做了什么,这些情况目前是透明的。

    设计理念&目标

    操作系统中基本思想方法是通过建立一些抽象(abstraction),屏蔽一些底层细节,让系统方便易用。就像用C这样的高级语言编写这样的程序不用考虑汇编,用汇编写代码不用考虑逻辑门,用逻辑门来构建处理器不用太多考虑晶体管。

    设计和实现操作系统的一个目标,是尽可能提供高性能(performance),也就是最小化操作系统的开销(minimize the overhead)。另一个目标是操作系统需要可信,在 OS 和应用程序之间提供保护(protection),确保某个程序的恶意行为不影响其他程序。因此进程间彼此隔离(isolation)是保护的关键。

  • 相关阅读:
    红黑树数据结构剖析
    miniui表单验证守则总结
    常用的JS页面跳转代码调用大全
    Jsp页面跳转和js控制页面跳转的几种方法
    处理和引发事件
    HeaderHandler 委托
    序列化SoapFormatter
    Debug.Assert
    C#的Thread类
    再次学习线程概念
  • 原文地址:https://www.cnblogs.com/tedukuri/p/14489131.html
Copyright © 2011-2022 走看看