ac酱のSecret Base Talk is cheap!

计算机基础知识

2020-07-23
ac酱

知道了今年的灰飞烟灭,那就准备其他方向的知识吧。记录一下计算机基础的相关知识。后续应该会另起一篇记录Java的一些知识点。

计算机网络

计算机组成原理

数据结构

操作系统

操作系统的基本特征

  • 并发性
    并发是指宏观上在一段时间内能同时运行多个程序,并行是指同一时刻能运行多个指令。
    并行需要硬件支持,如多流水线、多核处理器或分步式计算系统。
    操作系统通过引入进程和线程,使得程序能够并发运行。
  • 共享性
    共享是指系统中的资源可以被多个并发进程共同使用。
    有两种共享方式:互斥共享和同时共享。
    互斥共享的资源称为临界资源,例如打印机等,在同一时刻只允许一个进程访问,需要用同步机制来实现互斥访问。
  • 虚拟性
    虚拟技术把一个物理实体转换为多个逻辑实体。
    主要有两种虚拟技术:时分复用技术和空分复用技术。
    多个进程能在同一个处理器上并发执行,就是使用了时分复用技术。让每个进程轮流占用处理器,每次只执行一小个时间片并快速切换。
    虚拟内存使用了空分复用技术,将物理内存抽象成地址空间。每个进程都有各自的地址空间,地址空间的页被映射到物理内存,地址空间的页不需要全部在物理内存中(物理内存大小有限),当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。
  • 异步性(最重要)
    异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。

进程与线程的本质区别,各自的使用场景?

  • 进程
    进程是资源分配的基本单位,是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位,是进程实体的运行过程。动态性是进程的最基本的特征,即由创建而产生,由调度而执行,由撤销而消亡。
  • 进程实体
    程序段、相关的数据段和PCB(Process Control Block, 进程控制块,进程存在的唯一标志)三部分构成了进程实体。
  • 程序
    程序是一组有序指令的集合,其本身并不具有运动的含义,因此是静态的。
  • 线程
    线程是进程的一个实体,是独立运行和独立调度的基本单位(CPU上真正运行的是线程)。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但它可与同属一个进程的其他线程共享该进程拥有的全部资源。线程也被称为轻量级进程(light weight process)
  • 两者的区别
    1. 进程是资源分配的基本单位,线程是程序执行的基本单位,CPU上真正运行的是线程。
    2. 进程拥有自己的资源空间,每启动一个进程,系统就会为它分配地址空间;而线程与CPU资源分配无关,多个线程共享同一进程内的资源,使用相同的地址空间。
    3. 一个进程可以包含若干个线程。
    4. 线程的调度与切换比进程快很多。(进程由于不共享资源,因此在调度时需要保存上下文,读取上下文)
  • 各自的使用场景(优劣)
    1. 线程间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据(需要注意同步、互斥);而进程间的通信需要以通信(Inter Process Communication, IPC)的方式进行。
    2. 线程的调度与切换比进程快很多,同时创建一个线程的开销也比进程要小很多。
    3. 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会影响另一个进程,因为进程有自己独立的地址空间。

    总结起来:
    对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
    要求效率高,切换频繁时,资源的保护管理要求不是很高时,使用多线程。

进程的状态与状态转换

包含五种状态的进程状态图

进程的五种基本状态及转换

包含创建、就绪、执行、阻塞、终止五种状态。

包含七种状态的进程状态图

进程的七种基本状态及转换

将就绪扩展成活动就绪、静止就绪,将阻塞扩展成活动阻塞和静止阻塞。(区别在于进程是否在主存内占有资源)

进程同步问题

为什么需要进程同步?

当操作系统引入进程之后,由于进程可并发执行,所以如果不能对多个进程的运行进行管理,必然会因为这些进程对系统资源的无序竞争给系统造成混乱,致使每次的处理的结果存在着不确定性,即显现出不可再现性。

进程同步的基本概念

进程同步机制的主要任务,是对多个相关进程在执行次序上进行协调,使并发执行的进程之间能按照一定的规则(时序)共享系统资源,并能很好地进行合作,使得程序的执行具有可再现性。

进程间主要存在两种形式的制约关系:

  1. 间接相互制约关系,如共享系统资源。
  2. 直接相互制约关系,如进程间合作。

临界资源的概念:
某些资源必须互斥使用,如打印机、共享变量、表格、文件等。这类资源称为临界资源。
每个进程中访问临界资源的代码段称为临界区

同步机制应遵循的规则:

  1. 空闲让进
    如果临界区空闲,只要有进程申请就立即让其进入自己的临界区,以有效利用临界资源。
  2. 忙则等待
    每次仅允许一个进程处于临界区,其他试图进入临界区的进程必须等待,以保证对临界资源的互斥访问。
  3. 有限等待
    对于要求访问临界资源的进程,应保证在有限时间内能够进入自己的临界区,以免陷入“死等”状态。即进入临界区的进程只能逗留有限时间。
  4. 让权等待 进程不能进入临界区时,应立即释放处理机,以免陷入“忙等”状态。

主要的进程同步机制

  1. 软件方法
    虽然利用软件方法解决诸进程互斥进入临界区的问题,但有一定难度,并存在很大的局限性,如始终不能解决“忙等”问题且大幅增加系统的开销,现在已经很少采用了。
  2. 硬件同步机制
    目前许多计算机已提供了一些特殊的硬件指令,允许对一个字中的内容进行检测和修正,或是对两个字的内容进行交换等。可利用这些特殊的指令来解决临界区问题。实际上,在对临界区进行管理时,可以将标志看作一个锁,“锁开”进入,“锁关”等待,初始时锁是打开的。每个要进入临界区的进程必须先对锁进行测试当锁未开时,则必须等待,直到锁被打开。反之,当锁是打开的时候,则应立即把锁关闭,以阻止其他进程进入临界区。为了防止多个进程同时测试到锁为打开的情况,测试和关锁操作必须是连续的,不允许分开进行。
    1. 关中断
      关中断是实现互斥的最简单的方法之一(循环:屏蔽中断->进入临界区->启用中断->其余部分)。在进入锁测试之前关闭中断,直到完成锁测试并上锁之后才能打开中断。这样进程在临界区执行期间,计算机系统不响应中断,从而不会引发调度,也就不会发生进程或线程切换。由此保证了对锁的测试和关锁操作的连续性和完整性,有效保证了互斥。
      但是关中断同样存在许多缺点:
      1. 滥用关中断权力可能导致严重后果
      2. 关中断时间过长,会影响系统效率,限制了处理器交叉执行程序的能力
      3. 不适用于多CPU系统,因为在一个处理器上关中断并不能防止其他进程在其他处理器上执行相同的临界段代码
    2. 机器指令(Test-and-Set和Swap指令) 诸如TS指令和Swap指令的机器指令可应用于单处理机或多处理机中多进程共享存储器,实现互斥使用。使用起来简单且易于证明,可支持多个临界区,每个临界区可用自己的变量定义。
      与软件方法相比,减少了系统的额外开销,但由于需要太强的硬件约束条件,以及可能导致进程饥饿与死锁现象,一种没有成为通用的解决方法
  3. 信号量机制
    软件方法和硬件方法都存在“忙等”问题,浪费了处理机时间。而信号量方法能实现进程互斥与同步,不必“忙等”。
    两个或多个进程可以通过传递信号进行合作,可以迫使进程在某个位置暂时停止执行(阻塞等待),直到它可以收到一个可以“向前推进”的信号(被唤醒)。将实现信号灯作用的变量成为信号量。
    信号量机制已被广泛应用于单处理机和多处理机系统及计算机网络中,已经成为控制进程同步与互斥的通用方法
    1. 整型信号量
      最初由Dijkstra把整型信号量定义为一个整型量,除初始化外,仅能通过两个执行时不可中断的原子操作wait(S)和signal(S)来访问。这两个操作一直被分别称为PV操作(荷兰文中,通过叫passeren,释放叫vrijgeven)。
      wait(S)可描述为:
       wait(S){  
           while (S<=0);     // do no-op忙则等待  
           S--;  
       }
      

      用于申请资源(或使用权),进程执行wait原语时,可能会阻塞自己(while)。
      signal(S)可描述为:

       signal(S){
           S++; 
       }
      

      用于释放资源(或归还资源使用权),进程执行signal原语时,有责任唤醒一个阻塞进程

    2. 记录型信号量
      在整型信号量机制中的wait操作,只要信号量S<=0,就会不断地测试。因此该机制并未遵循“让权等待”的准则,而是使进程处于“忙等”的状态。
      而记录型信号量机制则是一种不存在“忙等”现象的进程同步机制。但在采取了“让权等待”的策略后,又会出现多个进程等待访问同一临界资源的情况。因此,信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表指针list,用于链接上述的所有等待进程
      记录型的数据结构可描述为:
       typedef struct{
           int value;      /*信号量的值*/
           struct process_control_block *list;     /*等待该信号量的阻塞进程*/
       }semaphore;
      

      wait(S)可描述为:

       wait(semaphore *S){
           S->value--;
           if (S->value<0) block(S->list);     /*若S->value<0,调用阻塞原语将自己阻塞*/
       }
      

      signal(S)可描述为:

       signal(semaphore *S){
           S->value++;
           if(S->value<=0) wakeup(S->list);
       }
      

      S->value的初值表示系统中某些资源的数目,因而又被称为资源信号量,若它的初值为1,此时转化为互斥信号量。对它的每次wait操作,意味着进程请求一个单位的该类资源,使系统中可供分配的该类资源数减少了一个,因此描述为S->value–;当S->value<0时,表示该类资源分配完毕,因此进程应调用block原语进行自我阻塞,放弃处理机,并插入到信号量链表S->list中。可见该机制遵循了“让权等待”准则。
      信号量的物理意义:
      S->value>=0,表示可用资源数,即还可执行wait(s)而不会阻塞的进程数。
      S->value<0,表示已无资源可用,因此请求该资源的进程被阻塞,此时S->value的绝对值等于该信号量阻塞队列中的等待进程数

    3. AND型信号量
      上述两种信号量,往往针对于多个并发进程仅共享一个临界资源的情况,但在有些应用场景中,一个进程往往需要获得两个或多个共享资源后才能执行任务。此时,若使用互斥信号量,则相互竞争资源的进程很有可能进入死锁状态,需求的共享资源越多,发生进程死锁的可能性越大。
      AND同步机制的基本思想是:将进程在整个运行过程中需要的所有资源,一次性全部分配给进程,待进程使用完后再一起释放。只要有一个资源未能分配给进程,其他所有可能分配给它的资源也不会分配给他。即对若干个临界资源的分配采取原子操作:要么把它所请求的资源全部分配到进程,要么一个都不分配。因此,需要在wait操作中增加一个“AND”条件,故称为AND同步,或称为同时wait操作。
    4. 信号量集
      前面所述的信号量机制中,wait和signal操作仅能对信号量施以加1或减1操作,意味着每次只能对某类临界资源进行一个单位的申请获释放,当一次需要N个临界资源时,则需要进行N次wait操作,这是低效且会增加死锁概率的。且有些情况下,为了保证系统的安全性,当所申请的资源数量低于一个下限值时,还需要进行管制,不进行分配。
      基于以上两点,可对AND信号量机制进行扩充,对进程所申请的所有资源以及每类资源不同的需求量,在一次PV操作中完成申请或释放。Swait和Ssignal格式为:
      Swait(S1,t1,d1,…,Sn,tn,dn):Sn表示请求的临界资源的信号量;tn表示该资源的分配的下限值,当该资源数量小于tn时不进行分配;dn表示该进程所需要的临界资源数量。进行Swait操作时,不是简单的Si=Si-1,而是Si=Si-di。
      Ssignal(S1,d1,…,Sn,dn):进行Ssignal操作时,不再是Si=Si+1,而是Si=Si+di。
      (个人理解,首先对现有资源进行分配下限值的测试,当现有资源Si不少于分配下限值ti时,才进行资源的分配。一旦允许分配,进程对该资源的需求值记为di,则分配后资源数量Si=Si-di。) 信号量集还存在几种特殊情况: (1) Swait(S,d,d)。此时在信号量集合中只有一个信号量S,但允许它每次申请d个资源,当现有资源数少于d时,不予分配。 (2) Swait(S,1,1)。此时信号量已蜕化成一般的记录型信号量(S>1时)或互斥信号量(S=1时)。 (3) Swait(S,1,0)(分配下限值为1,资源需求值为0,实际上就是作为一个开关,检查资源值是否低于分配下限)。这是一种很特殊且很有用的信号量操作,当S>=1时,允许多个进程进入某特定区;当S变为0后,将阻止任何进程进入特定区。
  4. 管程机制
    虽然信号量机制是一种既方便又有效的进程同步机制,但每个要访问临界资源的进程都必须自备同步操作wait(S)和signal(S),这就使大量的同步操作分散在各个进程中。这样不仅给系统的管理带来了麻烦,而且还会因同步操作的使用不当而导致系统死锁。于是便产生了一种新的进程同步工具——管程(Monitors)。
    代表共享资源的数据结构以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序共同构成了一个操作系统的资源管理模块,我们称之为管程。管程被请求和释放资源的进程所调用。
    管程由四部分组成:管程的名称;局部于管程的共享数据结构说明;对该数据集构进行操作的一组过程;对局部于管程的共享数据设置初始值的语句。
    需要确保每次仅有一个进程进入管程,执行管程中的某些过程,使用共享资源,达到对共享资源所有访问的统一管理,有效地进行进程互斥

    经典的进程同步问题

    生产者-消费者问题

    生产者-消费者问题是相互合作的进程关系的一种抽象。
    假定生产者与消费者相互等效,只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息。

    使用记录型信号量解决生产者-消费者问题

    ```c int in=0,out=0; item buffer[n]; semaphore mutex=1, empty=n, full=0; void producer(){ do{ producer an item nextp; … wait(empty); /注意应当先对缓冲池中的空缓冲区进行请求/ wait(mutex); /之后再请求互斥信号量,否则可能造成死锁/ /(请求互斥锁通过,但无空缓存,互斥锁未被释放就被挂起)/ buffer[in]=nextp; in=(in+1)%n; signal(mutex); signal(full); }while(TRUE); }

void consumer(){ do{ wait(full); wait(mutex); nextc=buffer[out]; out=(out+1)%n; signal(mutex); signal(empty); consumer the item in nextc; … }while(TRUE); }

void main(){ cobegin producer(); consumer(); coend }

可以看到,在生产者和消费者程序中用于实现`互斥`的wait(mutex)和signal(mutex)必须`成对`出现;对于资源信号量`empty和full`,他们的wait()和signal()操作也是成对出现的,但是分别`处于不同的程序`中;且多个wait操作的顺序不能颠倒,应先执行对资源信号量的wait操作,然后再执行对互斥信号量的wait操作,否则可能引起进程死锁。

##### 使用AND信号量解决生产者-消费者问题
对于producer,使用Swait(empty,mutex)替换wait(empty)和wait(mutex),使用Ssignal(mutex,full)替换signal(mutex)和signal(full);对于consumer,使用Swait(full,mutex)替换wait(full)和wait(mutex),使用Ssignal(mutex,empty)替换signal(mutex)和signal(empty)。

##### 使用管程解决生产者-消费者问题
使用管程解决这类问题时,首先需要为它们建立一个管程,其中包括两个过程:  
put(x)过程:生产者利用该过程将自己生产的产品投放到缓冲池中,并用整型变量count来表示在缓冲池中已有的产品数目,当count>=N时,表示缓冲池已满,生产者须等待。  
get(x)过程:消费者利用该过程从缓冲池中取出一个产品,当count<=0时,表示缓冲池中已无可取用的产品,消费者须等待。  
对于条件变量notfull和notempty,分别有两个过程cwait和csignal对它们进行操作。cwait过程为,当管程被一个进程占用时,其他进程调用该过程时阻塞,并挂在条件condition的队列上。csignal过程为,唤醒在cwait执行后阻塞在条件condition队列上的进程,如果这样的进程不止一个,则选择其中一个实施唤醒操作;如果队列为空,则无操作返回。  
管程可描述为:
```c
Monitor producerconsumer{
    item buffer[N];
    int in,out;
    condition notfull,notempty;
    int count;
    public:
    void put(item x){
        if(count>=N) cwait(notfull);    /*当count计数>=N,说明缓冲池已满*/
                                        /*那么当前进入管程的进程将被挂在条件notfull的队列上*/
        buffer[in]=x;
        in=(in+1)%N;
        count++;
        csignal(notempty);              /*唤醒被挂在条件notempty的队列上的进程*/
                                        /*若队列为空,则无操作返回*/
    }
    void get(item x){
        if(count<0) cwait(notempty);    /*当count计数<0,说明缓冲池为空*/
                                        /*那么当前进入管程的进程将被挂在条件notempty的队列上*/
        x=buffer[out];
        out=(out+1)%N;
        count--;
        csignal(notfull);               /*唤醒被挂在条件notfull的队列上的进程*/
                                        /*若队列为空,则无操作返回*/
    }
    {   in=0;out=0;count=0;}
}PC;

哲学家就餐问题

该问题描述的是有五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,它们交替地进行思考和进餐。当哲学家思考到饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完毕后,放下筷子继续思考。

利用记录型信号量解决哲学家进餐问题

经分析可知,筷子是临界资源,在一段时间内只允许一位哲学家使用。为了实现对筷子的互斥使用,可以用一个信号量表示一只筷子,由这五个信号量构成信号量数组:semaphore chopstick[5]=[1,1,1,1,1]; 当哲学家饥饿时,总是先去拿他左边的筷子,成功后再去拿他右边的筷子。这种方法显然可能引起死锁,假如五位哲学家同时饥饿而各自拿起自己左边的筷子时,就会使五个信号量chopstick均为0,当他们再试图去拿右边的筷子时,都将因为无筷子可拿而无限期地等待。对于这样的问题,可采取以下几种解决方法:
(1)至多只允许四位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐,并在用餐后释放出他用过的两只筷子,从而使更多的哲学家能够进餐。 (2)仅当哲学家的左、右两只筷子均可用时,才允许他拿起筷子进餐。 (3)规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子;而偶数号哲学家则相反。按此规定,将是1、2号哲学家竞争1号筷子;3、4号哲学家竞争3号筷子。即五位哲学家都先竞争奇数号筷子,获得后再去竞争偶数号筷子,最后总会有一位哲学家能获得两只筷子进餐。

利用AND信号量机制解决哲学家进餐问题

在该问题中,要求每个哲学家先获得两只筷子(临界资源)后进餐,这本质上就是AND同步问题,因此使用AND信号量机制可获得最简洁的解法。

读者-写者问题

一个数据文件或记录可被多个进程共享,我们把只要求读该文件的进程称为读者进程,把只要求写该文件的进程称为写者进程。允许多个读者进程同时读一个共享对象,因为读操作不会使数据文件混乱;但不允许一个写者和其他读者或写者进程同时访问共享对象。因为这种访问将会引起混乱。
所谓“读者-写者问题”是指保证一个写者进程必须与其他进程互斥地访问共享对象的同步问题。

利用记录型信号量解决读者-写者问题
semaphore rmutex=1, wmutex=1;
int readcount=0;
void reader(){
    do{
        wait(rmutex);
        if(readcount==0) wait(wmutex);      /*当readcount=0时,表示尚无读者进程在读时*/
                                            /*读者进程才需要执行wait(wmutex)操作,等待写锁释放*/
        readcount++;
        signal(rmutex);                     /*由于在这里,readcount也是个临界资源*/
                                            /*且读者进程将对其修改,因此也须使用互斥信号量进行保护*/
        ...
        read();
        ...
        wait(rmutex);                       /*当读操作完成后,由于需要对readcount修改*/
                                            /*申请互斥信号量对readcount变量进行保护*/
        readcount--;
        if(readcount==0) signal(wmutex);    /*当readcount=0时,表示已无读者进程在读*/
                                            /*此时将写锁释放*/
        signal(rmutex);
    }while(TRUE);
}
void writer(){
    do{
        wait(wmutex);
        write();
        signal(wmutex);
    }while(TRUE);
}
void main(){
    cobegin
        reader();writer();
    coend
}
利用信号量集机制解决读者-写者问题

这里的读者写者问题将增加一个限制,即最多只允许RN个读者同时读,为此又引入了一个信号量L,并赋予初值RN,通过Swait(L,1,1)操作来控制读者的数目,每当有一个读者要进入时,使L的值减1。

int RN;
semaphore L=RN,mx=1;
void reader(){
    do{
        Swait(L,1,1);       /*L-1,当L<1时挂载在L的等待队列上*/
        Swait(mx,1,0);      /*mx-0,当mx<1时挂载在mx的等待队列上*/
                            /*swait(mx,1,0)语句起着开关的作用*/
                            /*在writer中swait(mx,1,1),对进行了mx-1的尝试,如果成功了,mx将为0*/
                            /*此时reader中swait(mx,1,0)虽然不对mx进行请求,但会对其值进行校验*/
                            /*当写者未访问临界资源时,mx=1,少于RN数量的读者可以随意进行读操作*/
        ...
        read();
        ...
        Ssignal(L,1);
    }while(TRUE);
}
void writer(){
    do{
        Swait(mx,1,1;L,RN,0);   /*mx-1,说明写者开始访问临界资源*/
                                /*Swait()成功后,任何的读者都不能对临界资源进行读操作*/
                                /*L-0,L<RN时将进程挂在在等待队列上*/
                                /*L=RN说明此时无读者访问临界资源,写者可访问临界资源*/
                                /*这两个条件组合起来,就是既无读者(L=RN)也无写者(mx=1)访问临界资源时,当前写者可进入临界区*/
        ···
        write();
        ···
        Ssignal(mx,1);
    }while(TRUE);
}
void main(){
    cobegin
        reader(); writer();
    coend
}

进程间的通信方式

进程通信指进程之间的信息交换。进程的互斥与同步需要在进程间交换一定的信息,它们也可被归为进程通信,但是只能算得上低级进程通信。因为它们效率低,只能传递少量的(控制)消息;通信对用户不透明,OS只提供了共享存储器,其他的数据结构、进程的互斥与同步等都需要由程序员实现。
当进程间需要传送大量数据时,应当利用OS提供的高级通信工具,他们使用方便,OS隐藏了实现进程通信的具体细节,向用户提供了一组用于实现高级通信的命令(原语),用户可方便地直接利用它实现进程之间的通信。或者说,通信过程对用户是透明的,可以大大减少通信程序编制上的复杂性。此外它还能够高效地传送大量数据,用户可直接利用高级通信命令(原语)高效地传送大量的数据。

高级通信机制可归结为四大类:共享存储器系统、管道通信系统、消息传递系统以及客户机-服务器系统

  1. 共享存储器系统 在共享存储器系统中,相互通信的进程共享某些数据结构或共享存储区,进程之间能够通过这些空间进行通信。
    又可把它们分成两种类型:
    (1)基于共享数据结构的通信方式。
    各进程公用某些数据结构,借以实现诸进程间的信息交换,比如生产者-消费者问题中的有界缓冲区,操作系统仅提供共享存储器,由程序员负责对公用数据结构的设置及对进程间同步的处理。这种通信方式仅适用于传递相对少量的数据,通信效率低下,属于低级通信。 (2)基于共享存储区的通信方式。
    为了传输大量数据,从内存中划出了一块共享存储区域,诸进程可通过对该共享区的读或写交换信息,实现通信,数据的形式和位置甚至访问控制都是由进程负责,而不是OS。
    需要通信的进程在通信前,先向系统申请获得共享存储区的一个分区,并将其附加到自己的地址空间中,便可对其中的数据进行正常读、写,当完成或不再需要时,将其归还给共享存储区。
  2. 管道(pipe)通信系统 “管道”是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又称pipe文件。向管道(共享文件)提供输入的发送进程(写进程)以字符流形式将大量的数据送入管道;而接受管道输出的接收进程(读进程)则从管道中接收(读)数据。这种方式首创于UNIX系统,由于能够有效传输大量数据,被引入到许多其他OS中。
    为了协调双方的通信,管道机制必须提供以下三方面的协调能力:①互斥,即当一个进程正在对pipe执行读/写操作时,其他进程必须等待。②同步,当写进程把一定数量的数据写入pipe后,便去睡眠等待,直到读进程取走数据后再把它唤醒。当读进程读空pipe时,也应睡眠等待,直到写进程将数据写入管道后才将其唤醒。③确定对方是否存在,只有确定了对方已存在时才能进行通信。
  3. 消息传递系统(Message passing system) 该机制中,进程不必借助任何共享存储区或数据结构,而是以格式化的信息为单位将通信的数据封装在消息中,并利用OS提供的一组通信命令(原语),在进程间进行消息传递,完成进程间的数据交换。
    该方式隐藏了通信实现细节,是通信过程对用户透明化,降低了通信程序设计的复杂性和错误率,是当前应用最为广泛的一类进程间通信机制。
    基于消息传递系统的通信方式属于高级通信方式,因其实现方式的不同,可进一步分成两类:
    (1)直接通信方式,指发送进程利用OS提供的发送原语,直接把消息发送给目标进程;
    (2)间接通信方式,指发送和接受进程,都通过共享中间实体(称为信箱)的方式进行消息的发送和接收,完成进程间的通信。
  4. 客户机-服务器系统(Client-Server system) 在网络环境的各种应用领域,目前最为主流的通信实现机制是客户机-服务器系统。其主要的实现方法分为三类:套接字、远程过程调用和远程方法调用。
    套接字又可分为基于文件型基于网络型,基于文件型的通信进程都运行在同一台机器的环境中,套接字是基于本地文件系统支持的,一个套接字关联到一个特殊的文件,通信双方通过对这个特殊文件的读写实现通信,原理类似管道。
    套接字的又是在于不仅适用于同一台计算机内部的进程通信,也适用于网络环境中各不同计算机间的进程通信。每个套接字拥有唯一的套接字号,这样能够确保通信双方之间逻辑链路的唯一性,便于实现数据传输的并发服务,而且隐藏了通信设施以及实现细节,采用同一的接口进行处理。

    线程实现的方式

    线程一般分为两种,内核支持线程KST(Kernel Supported Threads)和用户级线程ULT(User Level Threads)。

    内核支持线程KST(kernel supported threads)

    OS中的所有进程,无论是系统进程还是用户进程,都是在OS内核的支持下运行的,是与内核紧密相关的。而KST同样也是在内核的支持下运行的,它们的创建、阻塞、撤销和切换等,都是在内核空间实现的。为了对内核线程进行控制和管理,在内核空间也为每个内核线程设置了一个线程控制块,内核根据该控制块而感知某线程的存在,并加以控制。当前大多数OS都支持KST。
    KST主要有四个优点,如下:
    (1)在多处理器系统中,内核能够同时调度同一进程中的多个线程并行(在多个处理器中)执行;
    (2)如果进程中的一个线程被阻塞了,内核可以调度该进程中的其它线程占有处理器运行,也可以运行其它进程中的线程
    (3)KST具有很小的数据结构和堆栈,线程的切换比较快切换开销小
    (4)内核本身也可以采用多线程技术,可以提高系统的执行速度和效率。
    缺点如下:
    对于用户线程的切换而言,其模式切换的开销较大,在同一个进程中,从一个线程切换到另一个线程时,需要从用户态转到核心态进行,这是因为用户进程的线程在用户态运行,而线程调度和管理是在内核实现的,系统开销较大。

    用户级线程ULT(user level threads)

    用户级线程是在用户空间内中实现的。对线程的创建、撤销、同步与通信等功能,都无需内核的支持,即用户级线程是与内核无关的。一个系统中用户级线程的数目可以达到数百个至数千个。由于这些线程的任务控制块都是设置在用户空间,而线程所执行的操作也无需内核帮助,因此内核完全不知道用户级线程的存在
    ULT主要有以下3个优点:
    (1)线程切换不需要转换到内核空间。对一个进程而言,其所有线程的管理数据结构均在该进程的用户空间中,管理线程切换的线程库也在用户地址空间运行,因此进程不必切换到内核方式来做线程管理,节省了模式切换的开销
    (2)调度算法可以是进程专用的。在不干扰OS调度的情况下,不同进程可以根据自身需要选择不同的调度算法,对自己的线程进行管理和调度,而与OS的低级调度算法是无关的。
    (3)用户级线程的实现与OS平台无关,因为对于线程管理的代码是属于用户程序的一部分,所有的应用程序都可以对之进行共享。因此,用户级线程甚至可以在不支持线程机制的操作系统平台上实现
    但ULT同时也存在以下2个缺点: (1)系统调用的阻塞问题。在基于进程机制的OS中,大多数系统调用将使进程阻塞,因此,当线程执行一个系统调用时,不仅该线程被阻塞,而且进程内的所有线程会被阻塞。而在内核支持线程方式中,则进程中的其他线程仍然可以运行
    (2)在单纯的用户级线程实现方式中,多线程应用不能利用多处理机进行多重处理的优点,内核每次分配给一个进程的仅有一个CPU,因此进程中仅有一个线程能执行,在该线程放弃CPU之前,其他线程只能等待。

    组合方式

    在组合方式线程系统中,内核支持多个KST的建立、调度和管理,同时也允许用户应用程序建立、调度和管理用户级线程。一些KST对应多个ULT,这是ULT通过时分多路复用KST来实现的。这种方式下,同一个进程中的多个线程可以同时在多处理器上并行执行,而且在阻塞一个线程时并不需要将整个进程阻塞。所有组合方式多线程机制能够结合两者的优点,克服各自的不足。
    根据KST和ULT连接方式的不同,可以形成三种模型: (1)

对于设置了用户级线程的系统,其调度仍是以进程为单位进行的。若系统设置了内核支持线程,则调度是以线程为单位进行的。

进程调度算法的特点及使用场景

死锁必要条件、如何解决死锁

虚拟内存的作用

分页系统实现虚拟内存的原理

页面置换算法、LRU

分页与分段的区别

静态链接的不足,动态链接的特点

堆与栈的区别?

什么是协程?


Comments

Content