正襟危坐说--操作系统(肆):线程

线程

线程就是我们为了让一个进程能够同时干多件事情而发明的“分身术”。

同一进程中的每个线程在本质上是一样的,即拥有同样的程序文本,但线程执行的上下文不一致。即线程是进程里面的一个执行上下文,或者执行序列。同一个地址空间里面的所有线程就构成了进程。

//线程是CPU切换的最小单位,进程是资源分配的最小单位。

例如:当我们使用Word时,实际上是打开了多个线程。这些线程一个负责显示,一个接受输入,一个定时存盘。这些线程一起运转(同时参与竞争CPU),让我们感觉到输入和屏幕显示同时发生,而不用键入一些字符,等待一会儿才看到屏幕显示。

 

线程管理

要管理线程就要维持线程的各种信息,存放这些信息的数据结构称为线程控制块。线程共享一个进程空间,因此许多资源是共享的(如地址空间、全局变量、文件等),这些共享资源存放在进程控制块即可。还有一些不被共享的资源和信息(如程序计数器、寄存器等),需要存放在线程控制块里。

 

线程的实现方式

由谁来管理线程有两种选择:一是让进程自己来管理线程;二是让操作系统来管理线程。由进程自己管理就是用户态线程实现,由操作系统管理就是内核态线程实现。

 

内核态线程实现

线程是进程的不同执行序列。故线程应该是CPU调度的基本单位。操作系统要管理线程,就要保持维护线程的各种资料,即将线程控制块存放在操作系统内核空间。这样操作系统内核就同时保有进程控制块和线程控制块。

优点:

1,用户编程保持简单。用户程序员在编程的时候无需管理线程的调度,即无需担心线程什么时候会执行,什么时候会挂起。

2,如果一个线程执行阻塞操作,操作系统可以从容地调度另外一个线程执行。因为操作系统能监控所有线程。

缺点:

1,效率较低。因为线程在内核态实现,每次线程切换都需要陷入到内核,由操作系统来进行调度。

2,占用内核稀缺的内存资源。操作系统需要维护线程表,操作系统所占内存空间一旦装载结束后就已经固定,无法动态改变。由于线程的数量大大高于进程的数量,那随着线程数量的增加,操作系统内核空间将迅速耗尽。

3,需要修改操作系统

 

用户态线程实现

用户自己做线程的切换,自己管理线程的信息,而操作系统无需知道线程的存在。

用户自己写一个执行系统作调度器,即除了正常执行任务的线程外,还有一个专门负责执行线程调度的线程。

由于大家都在用户态下运行,要想取得CPU控制权只能靠大家的自愿合作。一个线程在执行完一段时间后主动把资源释放给别人使用,而在内核态下则无需如此。在用户态下,执行系统的调度器也是线程,没有能力强行夺走控制权,所以必须合作。

优点:

1,灵活性。操作系统不用知道线程的存在,所以在任何操作系统上都能应用。

2,线程切换快。切换在用户态进行,无需陷入到内核态。

缺点:

1,编程很困难。我们在写程序的时候必须仔细斟酌在什么时候应该让出CPU给别的线程使用。

2,一个线程受阻造成整个进程都受阻。如果在执行过程中一个线程受阻,它将无法将控制权交出来(因为受阻后无法执行交出CPU的指令了),这样整个进程都无法推进。操作系统随即把CPU控制权交给另外一个进程。

 

现代操作系统的线程实现模型

鉴于用户态和内核态都存在缺陷,现代操作系统使用的是将二者结合起来。用户态的执行系统负责进程内部线程在非阻塞时的切换;内核态的操作系统负责阻塞线程的切换,即我们同时实现内核态和用户态线程管理。其中内核态线程数量较少,而用户态线程数量多。每个内核态线程可以服务一个或多个用户态线程。换句话说,用户态线程被多路复用到内核态线程上

例如,某个进程有5个线程,我们可以将5个线程分成两组,一组3个线程,另一组2个线程。每一组线程使用一个内核线程。这样,该进程将使用两个内核线程。如果一个线程阻塞,则与其同属于一组的线程皆阻塞,但另外一组线程却可以继续执行

这样,在分配线程时,我们可将需要执行阻塞操作的线程设为内核态线程,而不会执行阻塞操作的线程设为用户态线程。这样我们就可以获得两种态势实现下的优点,而避免其缺点。

 

从用户态进入内核态

如果在程序运行过程中发生中断异常,系统将自动切换到内核态来运行中断或异常处理机制。

此外,程序进行系统调用也将造成从用户态进入到内核态的转换。

★例如:一个C++程序调用函数cin。cin是一个标准库函数,它将调用read函数。而read则是由操作系统提供的一个系统调用。其执行过程如下:

1,执行汇编语言里面的系统调用指令(如syscall)

2,将调用的参数存放在指定的寄存器或栈上(事先约定好)

3,当处理器执行到“syscall”指令时,察觉到这是一个系统调用指令,将进行如下操作:

①设置处理器为内核状态

②保存当前寄存器(栈指针、程序计数器、通用寄存器)

③将栈指针设置指向内核栈地址

④将程序计数器设置为一个事先约定的地址上。该地址上存放的是系统调用处理程序的起始地址。(所有的系统调用都由系统调用处理程序来处理)

4,系统调用处理程序执行系统调用,并调用内核里面的read函数。

这样,就实现了从用户态到内核态的转换,并完成系统调用所要求的功能。

 

原文地址:https://www.cnblogs.com/riasky/p/3431036.html