操作系统-进程

进程

进程是正在执行程序的实例,包括程序计算机,寄存器和变量。

从概念上说,每个进程都有自己的虚拟CPU,当然,实际的CPU是在进程之间来回切换。

实际上,只有一个物理程序计数器,所以在每个程序运行时,它的逻辑程序计数器被
装入到实际的程序计数器中。当该程序执行结束(暂停)时,物理程序计数器被装入到
逻辑程序计数器中。

守护进程

unix中,可以通过一个系统调用来创建新进程:fork。这个系统调用会创建一个与调
用进程相同的副本。在调用fork后,这两个进程(父进程和子进程)拥有相同的内存映像
,同样的环境字符串和同样的打开文件

写时复制:

进程的状态:

运行:cpu和条件都满足
阻塞:cpu满足,必要条件不满足
就绪:cpu不满足,必要条件满足

进程控制块(PCB)–进程存在的唯一标志

PCB记录了操作系统所需的、用于描述进程的当前情况以及控制进程运行的全部信息。
操作系统是根据PCB对进程进行控制和管理的。
(1)当OS要调度某进程执行时,要从该进程的PCB中查出其现行状态及优先级。
(2)调度到某进程后,根据其PCB中所保存的处理机状态信息,设置该进程恢复运行
的现场,并根据PCB中的程序和数据的内存始址,找到其程序和数据。
(3)进程载执行过程中,与其他进程的通信,同步都需要用到PCB
(4)当进程切换时,处理机环境也保存在PCB中

进程的实现:

有了进程模型,进程应该如何实现
为了实现进程模型,操作系统维护者一张(一个数据结构),即进程表。每个进程占用
一个进程表项。该表项包含了进程状态的重要信息,包括程序计数器、堆栈指针,内存
分配状况、所打开文件的状态、帐号和调度信息,以及其他在进程进行状态转换时所必
须保存的信息,从而保证该进程随后能再次启动,就像从未被中断过一样。

中断向量在进程切换中的重要作用

单个CPU如何维护多个顺序进程
假设当一个磁盘中断发生时,用户进程正在运行,则中断硬件将程序计数器,程序状态字、
有时还有一个或多个寄存器压入堆栈,计算机随即跳转到中断向量所指示的地址。这些是
硬件完成的所有操作,然后软件,特别是中断服务例程就接管了一切剩余的工作。

线程

为什么需要多线程
1.有了多线程,并行实体拥有共享同一个地址空间和所有可有数据的能力。
2.线程比进程更轻量级。
3.IO密集的处理,使用多线程效率更高。

线程概念试图实现的是,共享一组资源的多个进程的执行能力,以便这些线程可以为完成某
一个任务而共同工作。

进程:
用某种方法将相关的资源集中在一起。进程有存放程序正文和数据以及其他的资源的地址空间。
这些资源包括打开的文件,子进程,即将发生的定时器、信号处理程序、帐号信息等。
线程:CPU调度的单位

线程堆栈

线程的问题

线程的过程
子进程和父进程可以拥有相同的多线程吗,如果解决两者中线程的同步
多线程中共享数据的同步

POSIX线程

为了实现可移植的线程程序,IEEE定义了线程的标准。它定义的线程包叫作pthread.大部分
UNIX系统都支持该标准。
pthread_create 创建一个新的线程
pthread_exit 结束调用的线程
pthread_join 等待一个特定的线程退出
pthread_yield 释放CPU来运行另外一个线程
pthread_attr_init 创建并初始化一个线程的属性结构
pthread_attr_destroy 删除一个线程的属性结构

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUMBER_OF_THREADS 10

void *print_hello_world(void *tid)
{
printf("hello world,Greeting form thread %d\n", tid);
pthread_exit(NULL);
}

int main(int args,char *argv[])
{
pthread_t threads[NUMBER_OF_THREADS];
int status,i;
for(i=0;i<NUMBER_OF_THREADS; i++){
printf("main here,creating thread %d\n",i);
status = pthread_create(&threads[i],NULL,print_hello_world,(void*)i);
if(status!=0){
printf("oops,pthread_create returned error code %d\n", status);
}
}
exit(NULL);
}

output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
main here,creating thread 0
main here,creating thread 1
hello world,Greeting form thread 0
hello world,Greeting form thread 1
main here,creating thread 2
main here,creating thread 3
hello world,Greeting form thread 2
main here,creating thread 4
hello world,Greeting form thread 3
main here,creating thread 5
hello world,Greeting form thread 4
main here,creating thread 6
hello world,Greeting form thread 5
main here,creating thread 7
hello world,Greeting form thread 6
main here,creating thread 8
hello world,Greeting form thread 7
main here,creating thread 9
hello world,Greeting form thread 8
hello world,Greeting form thread 9

线程的实现方式

线程已经在许多系统中实现了,但各系统的实现方式并不完全相同。在有的系统中所实现
的是用户级线程(ULT),而另一些系统所实现的是内核支持线程(KST),还有的系统是
混合实现。

什么是用户级线程

用户级线程仅存在于用户空间中,对于这种线程的创建、撤销、线程之间的同步与通信
等功能,同无需利用系统调用来实现。对于用户级线程的切换,也无须内核的支持。对于
设置用户级线程的系统,其调度仍是以进程为单位进行的,内核完全不知道用户级线程的
存在。
在用户空间管理线程时,每个进程都需要有其专用的线程表,用来跟踪该进程中的线程。
线程表和进程表相似,不过它记录的仅仅是各个线程的属性,比如每个线程的程序计数器、
堆栈指针、寄存器和状态等。该线程表由运行时系统管理。

什么是内核支持线程

对于通常的进程,无论是系统进程还是用户进程,进程的创建、撤销、以及要求由系统
设备完成的I/O操作,都是利用系统调用而进入内核,再由内核中的相应处理程序予以完成。
进程的切换同样是在内核的支持下实现的。不论什么进程,它们都是在操作系统内核的支持
下运行的,是与内核紧密相关的。
这里所谓的内核支持线程KST(kernel supported threads),也都同样是在内核的
支持下运行的,即无论是用户进程中的线程,还是系统进程中的线程,他们的创建、撤销和
切换等也是依靠内核,在内核空间实现的。此外,在内核空间还为每一个内核支持线程设置
了一个线程控制块,内核是根据该控制块而感知某线程的存在,并对其加以控制。
内核支持线程有以下的优点:
(1)内核中拥有每个线程的线程控制块,那么内核就可以使用调度算法来调度进程中多个
线程并发执行。
(2)如果进程中的一个线程被阻塞了,内核可以调度该进程中的其他线程占用处理器运行,
也可以运行其他进程中的线程。
(3)内核支持线程具有很小的数据结构和堆栈,线程的切换比较快,切换开销小。
(4)内核本身也可以采用多线程技术,可以提高系统的执行速度和效率。
内核线程的缺点也很明显,对于用户线程的切换,要从用户态进行到内核态。

线程切换

线程切换

用户级线程为什么要和内核级线程进行映射,映射的模型

线程的实现

不论是进程还是线程,都必须直接或间接地取得内核的支持。由于内核支持线程可以
直接利用系统调用为它服务,故线程的控制相当简单;而用户级线程必须借助于某种形式
的中间系统的帮助方能取得内核的服务,故在对线程的控制上要稍复杂些。

内核支持线程的实现

在仅设置了内核支持线程的OS中,一个可能的线程控制方法是,系统在创建一个新进程
时,便为它分配一个任务数据区PTDA(per task data area),其中包括若干线程控
制块TCB空间。在每一个TCB中可保存线程标识符,优先级,线程运行的CPU状态等信息。
虽然这些信息与用户线程TCB中的信息相同,但现在却是被保存在内核空间中。
每当进程要创建一个线程时,便为新线程分配一个TCB,将有关信息填入该TCB中,并
为之分配必要的资源,如为线程分配数百至数千的栈空间和局部变量存储区。

用户级线程的实现

用户级线程是在用户空间实现的。所有的用户级线程都具有相同的结构,它们都运行在
一个中间系统的上面。当前有两种方式实现的中间系统,即运行时系统和内核控制线程。
(1)所谓“运行时系统”,实质上是用于管理和控制线程的函数(过程)的集合,其中包括
用于创建和撤销线程的函数、线程同步和通信的函数以及实现线程调度的函数等。正因为
有这些函数,才能使得用户线程和内核无关。运行时系统中的所有函数都驻留载用户空间,
作为用户级线程和内核之间的接口.
在传统的OS中,进程在切换时必须先由用户态转为核心态,再由核心来执行任务;而用
户级线程在切换时则不需要转入核心态,而是由运行时系统的线程切换过程来执行切换任
务。该过程将线程的CPU状态保存在该线程的堆栈中,然后按照一
定的算法选择一个处于就绪状态的新线程进行运行,将新线程堆栈中的CPU状态装入到CPU相应的寄存器中;
一旦将栈指针和程序计数器切换后,便开始了新线程的运行。
由于用于级线程的切换无需进入内核,且切换操作简单,因为使用用户级线程的切换速度
非常快。
不管在传统的OS中,还是在多线程OS中,系统资源都是由内核管理的。在传统的OS中,
进程是利用OS提供的系统调用来请求系统资源的,系统调用通过软中断机制进入OS内核,
由内核来完成相应资源的分配。用户级线程是不能利用系统调用的。当线程需要系统资源
时,是将该要求传送给运行时系统,由后者通过相应的系统调用来获得系统资源的。

(2)内核控制线程
这种线程又称为轻型进程LWP(light-weight process)。每一个进程都可拥有多个LWP,
同用户级线程一样,每个LWP也有自己的TCB。LWP可通过系统调用来获得内核提供的
服务,这样,当一个用户级线程运行时,只要将它链接到一个LWP上,此时它便具有了内
核线程的所有属性。
LWP线程是有限的,所有用户线程可以复用这些LWP。如果需要与内核通信,则需要和LWP
进行连接。这样,通过LWP可把用户级线程和内核线程链接起来,用户级线程可通过LWP来
访问内核。从内核的角度来看,它只能意识到LWP的存在,而意识不到用户级线程的存在。
这种方式也是实现了
用户级线程和内核无关。
lwp
当用户级线程不需要与内核通信时,并不需要LWP;而当要通信时,便需要借助于LWP,
而且每个要通信的用户级线程都需要一个LWP。如果要通信的用户级线程大于LWP的数量,
那么就需要有用户级线程等待。
在内核线程执行操作时,如果发生阻塞,那么与之相连的LWP也会阻塞,进而连接到LWP
上的用户级线程也会被阻塞。如果进程只有一个LWP,那么和传统的OS一样,当进程执行系
统调用时,该进程实际上是阻塞的。但如果一个进程中含有多个LWP,一个LWP阻塞,进程
中的其他LWP可以执行;即使进程中的所有LWP全部不阻塞,进程中的线程也仍然能继续执
行,只是不能再去访问内核。

用户级线程和内核控制线程(LWP)的连接

(1)一对一模型
该模型为每一个用户线程都设置一个内核控制线程与之相连,当一个线程阻塞时,允许调度
另一个线程运行。在多处理及系统中,则有多个线程并行执行。
该模型并行能力强,但每创建一个用户线程相应地就需要创建一个内核线程,开销较大,
需限制整个系统的线程数。win2000,winNT,OS/2实现该模型。
(2)多对一模型
该模型将多个用户线程映射到一个内核控制线程,为了管理方便,这些用户线程一般属于一
个进程。运行在该进程的用户空间,对这些线程的调度和管理也是在该进程的用户空间中完
成。当用户需要访问内核时,才将其映射到一个内核控制线程上,但每次只允许一个线程进
程映射。
该模型的主要优点是开销小,效率高,但当一个线程在访问内核时发生阻塞,则整个进程都
会被阻塞,而且多处理机系统中,一个进程的多个线程无法实现并行。
(3)多对多模型
该模型结合以上两种模型的优点,将多个用户线程映射到多个内核控制线程,内核控制线程
的数目可以根据应用和系统的不同而变化。

思考

在一对一和多对一模型下,如果内核线程阻塞,那么用户线程肯定也会被阻塞,特别是多对一,
会导致一组线程都会被阻塞,所以才会有第三种模型的产生。但是内核线程阻塞后,用户线程
是如何能够转到另一个线程运行的?
这个问题的解决就是利用调度程序激活机制,字面理解,就是内核线程阻塞时,内核会主动通知
运行时系统有阻塞发生,运行时系统可以重新调度其他线程进行执行。
实际的工作思路也是差不多的,当内核了解到一个线程被阻塞之后(例如,由于执行了一个阻塞
系统调用或者产生了一个页面故障),内核通知该进程的运行时系统,并且在堆栈中以参数形式
传递有问题的线程编号和所发生事件的一个描述。内核通过在一个已知的起始地址启动运行时系
统,从而发出了通知,这是对UNIX中信号的一个粗略模拟,这个机制称为上行调用(upcall)。

本文标题:操作系统-进程

文章作者:learner66

发布时间:2018年05月30日 - 15:45:53

最后更新:2018年06月01日 - 20:00:19

原始链接:https://learner66.github.io/2018/05/30/os-process/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

------ 本文结束,感谢您的阅读------