操作系统学习笔记(二) ---进程

Chapter 3 Process

例题:

1.Using the program shown in Figure 3.30, explain what the output will be at LINE A.

Answer:

输出为5

分析:创建了一个子进程,子进程复制了父进程的栈、堆等信息。

全局变量在子进程中改变不影响其在父进程中的值。(全局变量存储在数据段,属于初始化数据,子进程会复制父进程的初始化数据,两个进程是相互独立的)

 

2.Including the initial parentprocess,how many processesarecreated by the program shown in Figure 3.31?

 

Answer:

子进程创建过程如图,最终有8个进程。

 

3.When a process creates a new process using the fork() operation,

which of the following states is shared between the parent process and the child process?

a. Stack

b. Heap

c. Shared memory segments (内存段)

Answer:

只有Shared memory segments(共享内存段)是共享的,子进程将复制父进程的栈、堆、数据段。复制不等同于共享,如第一题,对各自的变量进行赋值等操作不会影响另一个进程。

 

Construct a process tree similar to Figure 3.8. To obtain process information for the UNIX or Linux system, use the command ps -ael.

Answer:

 

execlp()函数:从PATH环境变量中查找文件并执行。

execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0]、argv[1]……, 最后一个参数必须用空指针(NULL)作结束。

execlp("/bin/ls","ls",NULL)  这行的意思应该是调用bin目录下的ls命令(用该命令覆盖子进程地址空间)

这个函数执行后程序当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其 main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。有六种不同的exec函数可供使用,它们常常被统称为exec函数。这些exec函数都是UNIX进程控制原语。

所以直接执行该程序并不会打印”LINE J”,除非不执行execlp()函数。

我们将execlp("/bin/ls","ls",NULL)替换成execlp("/bin/ps","ps","-ael",NULL);可以在运行时得到如下结果(部分图示)

可以得进程树

4.Using the program in Figure 3.34,identify the values of pid at lines A,B, C, and D. (Assume that the actual pids of the parent and child are 2600 and 2603, respectively.)

Answer:

getpid()函数:返回当前进程pid

getppid()函数:返回父进程pid

A行:pid1 = 子进程pid (0)

         pid = 创建子进程时的pid值---父进程pid

所以A行打印0

B行打印2600

 

parent process:

pid1 = 父进程pid

pid = fork()函数返回值

所以C行打印2600   D行打印2603

 

fock函数调用一次却返回两次;向父进程返回子进程的ID,向子进程中返回0,这是因为父进程可能存在很多过子进程,所以必须通过这个返回的子进程ID来跟踪子进程,而子进程只有一个父进程,他的ID可以通过getppid取得。

 

题目要求自己写三个函数简单实现pid

位示图法:

位示图中'0'表示空闲,‘1’表示使用。

32768个pid共需要32768位,即1024个int类型的数据,数组下标是0~1023,设每个int数据的位编号是0~31,则index是i,编号是j位对应的pid=i*32+j

(本题的MIN_PID 300 MAX_PID 5000  将申请范围限制在此范围即可)

如何判断一个int型数据的第j位是否为1?

方法是将int类型数据右移j位,再按位&1,如果结果是1,则其j位是1,否则是0。

 

申请和释放是分别需要一个数组来修改位示图,具体见下文程序。(参考别人的)

#include<iostream>
using namespace std;

class LinuxPid
{
	public:
		LinuxPid();
		short GetPid();
		void ApplyPid();             //向系统申请一个空闲进程pid 
		void ReleasePid(short pid);    //释放一个进程标识符pid 
	private:
		unsigned int *mpid;       //位示图数组 
		unsigned int *apply;      //申请用数组 
		short pid;               //进程标识符pid 
		unsigned int *release;    //释放用数组
};

LinuxPid::LinuxPid()
{
	mpid = new unsigned int[1024];
	int i;
	for(i=0;i<1024;i++)
	  mpid[i] = 0x00000000;
	mpid[0] = 0x00000001;     //0号pid为系统进程使用
	apply = new unsigned int[32]       //申请pid的数组 
	{
		0x00000001,0x00000003,0x00000007,0x0000000F,
		0x0000001F,0x0000003F,0x0000007F,0x000000FF,
		0x000001FF,0x000003FF,0x000007FF,0x00000FFF,
		0x00001FFF,0x00003FFF,0x00007FFF,0x0000FFFF,
		0x0001FFFF,0x0003FFFF,0x0007FFFF,0x000FFFFF,
		0x001FFFFF,0x003FFFFF,0x007FFFFF,0x00FFFFFF,
		0x01FFFFFF,0X03FFFFFF,0x07FFFFFF,0x0FFFFFFF,
		0x1FFFFFFF,0x3FFFFFFF,0x7FFFFFFF,0xFFFFFFFF
	};
	release = new unsigned int[32]
	{
		0xFFFFFFFE,0xFFFFFFFD,0xFFFFFFFB,0xFFFFFFF7,
		0xFFFFFFEF,0xFFFFFFDF,0xFFFFFFBF,0xFFFFFF7F,
		0xFFFFFEFF,0xFFFFFDFF,0xFFFFFBFF,0xFFFFF7FF,
		0xFFFFEFFF,0xFFFFDFFF,0xFFFFBFFF,0xFFFF7FFF,
		0xFFFEFFFF,0xFFFDFFFF,0xFFFBFFFF,0xFFF7FFFF,
		0xFFEFFFFF,0xFFDFFFFF,0xFFBFFFFF,0xFF7FFFFF,
		0xFEFFFFFF,0xFDFFFFFF,0xFBFFFFFF,0XF7FFFFFF,
		0xEFFFFFFF,0xDFFFFFFF,0xBFFFFFFF,0x7FFFFFFF
	};

}
short LinuxPid::GetPid()
{
	return pid;
}

void LinuxPid::ApplyPid()
{
	int i,j;
	for(i=0;i<1024;i++)
	{
		for(j=0;j<32;j++)
		{
			if((( mpid[i] >> j ) & 1) == 0 && (i*32+j)>=300 &&(i*32+j)<=5000) 
			{
				mpid[i] = mpid[i] | apply[j] ;   //只修改第j位
				pid = (short) (i*32 + j);
				return; 
			}
		}
	}
}

void LinuxPid::ReleasePid(short pid)
{
	int i,j;
	i = pid/32;
	j = pid%32;
	mpid[i] = mpid[i] & release[j];    //只将第j位清0 
	
}

 

概念部分

进程与程序

进程是动态的,程序是静态的:

进程是程序的执行,程序是有序代码的集合;

进程是暂时的,程序是永久的。

进程与程序的组成不同:进程的组成包括程序,数据和数据控制块

进程的状态

New.(新的) The process is being created.

Running.(运行)Instructions are being executed.

Waiting. (等待)The process is waiting for some event to occur (such as an I/O completion or reception of a signal).

Ready. (就绪)The process is waiting to be assigned to a processor.

Terminated. (终止)The process has finished execution.

 

PCB(进程控制块)

           

1个进程对应1个PCB

PCB含有三大类信息:

一、进程标识信息:如本进程的标识,父进程标识;用户标识

二、处理机状态信息保存区:保存进程的运行现场信息

---用户可见寄存器:用户程序可以使用的数据,地址等寄存器

---控制和状态寄存器:如程序计数器PC,程序状态字PSW

---栈指针:过程调用/系统调用/中断处理和返回时需要它

三、进程控制信息

---调度和状态信息

---进程间通信信息

---存储管理信息

---进程所用资源

---有关数据结构连接信息

 

具体如下:

进程状态:状态可包括新的、就绪、运行、等待、停止等。

程序计数器:计数器表示进程要执行的下个指令的地址。

CPU 寄存器:根据计算机体系结构的不同,寄存器的数量和类型也不同。它们包括 累加器、索引寄存器、堆战指针、通用寄存器和其他条件码信息寄存器。与程序计数器一 起,这些状态信息在出现中断时也需要保存,以便进程以后能正确地继续执行

CPU 调度信息:这类信息包括进程优先级、调度队列的指针和其他调度参数

内存管理信息:根据操作系统所使用的内存系统,这类信息包括基址和界限寄存器

的值、页表或段表(见第 8 章)。

记账信息:这类信息包括 CPU 时间、实际使用时间、时间界限、记账数据、作业或进程数量等。

I/O 状态信息:这类信息包括分配给进程的I/O设备列表、打开的文件列表等。

简而言之PCB 简单地作为这些信息的仓库,这些信息在进程与进程之间是不同的。

 

PCB的组织方式:

链表:同一状态的进程其PCB成一链表,多个状态对应多个不同的链表

各个状态的进程形成不同的链表:就绪链表、阻塞链表(链表便于插入和删除)

进程的生命期原理(对进程的相关操作)

进程创建

课本整理:

通常,进程需要一定的资源(如 CPU 时间、内存、文件、I/O 设备)来完成其任务。 在一个进程创建子进程时,子进程可能从操作系统那里直接获得资源,也可能只从其父进程那里获得资源。父进程可能必须在其子进程之间分配资源或共享资源(如内存或文件)。

限制子进程只能使用父进程的资源能防止创建过多的进程带来的系统超载。

在进程创建时,除了得到各种物理和逻辑资源外,初始化数据(或输入)由父进程传 递给子进程。例如,考虑一个进程,其功能是在终端屏幕上显示文件(如 img.jpg) 的状态。 在创建时,作为它的父进程的输入,它会得到文件 img.jpg 的名称,并能使用此名称打开文件,以及写出内容。它也能得到输出设备的名称。有的操作系统将资源传递给子进程。 在这类系统上,新进程可得到两个打开文件,即 img.jpg 和终端设备,新进程只需在两者之间传递数据。

当进程创建新进程时,有两种执行可能:

①父进程与子进程并发执行。 ②父进程等待,直到某个或全部子进程执行完。

新进程的地址空间也有两种可能:

①子进程是父进程的复制品(具有与父进程相同的程序和数据)。

②子进程装入另一个新程序。

网课整理:

引起进程创建的3个主要事件:

①系统初始化时

②用户请求创建一个新进程

③正在运行的进程执行了创建进程的系统调用

 

当进程创建新进程时,有两种执行可能:

①父进程与子进程并发执行。 ②父进程等待,直到某个或全部子进程执行完。

新进程的(物理)地址空间也有两种可能:

①子进程是父进程的复制品(具有与父进程相同的程序和数据)。

②子进程装入另一个新程序。

 

相关的系统调用:

fork()与vfork()

fork()的简单实现:

对子进程分配内存

复制父进程的内存和CPU寄存器到子进程里

开销昂贵

99%的情况下,我们在调用fork()之后调用exec()

系统调用exec()加载程序取代当前运行的进程

…………….

vfork()?

只复制一小部分内容,减少开销。

一个创建进程的系统调用,不需要创建一个同样的内存映像

有些时候称其为轻量级fork()

子进程几乎立即调用exec()

 

Copy On Write

在创建子进程时,将父进程的PDE直接赋值给子进程的PDE,但是需要将允许写入的标志位置0;当子进程需要进行写操作时,再次发出中断调用do_pgfault(),此时应给子进程新建PTE,并取代原先PDE中的项,然后才能写入。

写时复制,需要理解内存管理。

 

进程运行

内核选择一个就绪的进程,让它占用CPU并执行

 

进程等待

在以下情况下,进程等待(阻塞)

①请求并等待系统服务,无法马上完成

②启动某种操作,无法马上完成

③需要的数据没有到达

进程只能自己阻塞自己,因为只有进程自身才能知道何时需要等待某种事件的发生

相关的系统调用:

wait()系统调用是被父进程用来等待子进程的结束

---一个子进程向父进程返回一个值,所以父进程必须接受这个值并处理

---wait()系统调用担任这个要求

①它使父进程取睡眠来等待子进程的结果

②当一个子进程调用exit()的时候,操作系统解锁父进程,并且将通过exit()传递得到的返回值作为wait调用的一个结果(连通子进程的pid一起)如果这里没有子进程存货,wait()立刻返回。

③如果有父进程的僵尸等待,wait()立即返回其中一个值并解除僵尸状态。

wait用于父进程回收子进程的PCB,子进程不能自己回收PCB

 

进程唤醒

唤醒进程的原因:

①被阻塞的进程需要的资源可被满足

②被阻塞进程等待的事件到达

③将该进程的PCB插入到就绪队列

进程只能被别的进程或操作系统唤醒

 

进程结束

课本整理:

当进程完成执行最后的语句并使用系统调用 exitO请求操作系统删除自身时,进程终 止。这时,进程可以返回状态值(通常为整数)到父进程(通过系统调用 wait()) 。所有进 程资源(包括物理和虚拟内存、打开文件和 νo 缓冲)会被操作系统释放。

在其他情况下也会出现终止。进程通过适当的系统调用(如 Win32 中的 TenninatePorcessO) 能终止另一个进程。通常,只有被终止进程的父进程才能执行这一系统调用。否则,用户可以任意地终止彼此的作业。记住,父进程需要知道其子进程的标识

符。因此,当一个进程创建新进程时,新创建进程的标识符要传递给父进程。

父进程终止其子进程的原因有很多,如:

①子进程使用了超过它所分配到的一些资源。(为判定是否发生这种情况,要求父进程有一个检查其子进程状态的机制。)

②分配给子进程的任务己不再需要。

③父进程退出,如果父进程终止,那么操作系统不允许子进程继续。

有些系统,包括 VMS ,不允许子进程在父进程己终止的情况下存在。对于这类系统, 如果一个进程终止(正常或不正常),那么它的所有子进程也将终止。这种现象,称为级联

终止( cascading termination),通常由操作系统进行。 为了说明进程执行和终止,可考虑一下UNIX: 可以通过系统调用 exit()来终止进程, 父进程可以通过系统调用wait()以等待子进程的终止。系统调用waitO返回了终止子进程的

进程标识符,以使父进程能够知道哪个子进程终止了。如果父进程终止,那么其所有子进程会以 init 进程作为父进程。因此,子进程仍然有一个父进程来收集状态和执行统计

 

网课整理:

在以下四种情况下,进程结束

①正常退出(自愿的)

②错误推出(自愿的)

③致命错误(强制性的)

④被其他进程所杀(强制性的)

相关的系统调用:

进程结束执行之后会调用exit()这一系统调用

其过程:

①将程序的“结果”作为一个参数

②关闭所有打开的文件,连接等

③释放内存

④释放大部分支持进程的操作系统结构

⑤检查父进程是否存活着:如果是的话,将保留结果的值直到父进程需要它;这种情况下,进程没有真正死亡,但是它进入了僵尸(zombie)状态

如果没有,它释放所有的数据结构,这个进程死亡。

⑥清理所有等待的僵尸进程。

进程终止是最终的垃圾回收(资源回收)

 

进程挂起:挂起(suspend)-把一个进程从内存转到外存

进程在挂起状态时,意味着进程没有占用内存空间。处在挂起状态的进程映像在磁盘上。

阻塞挂起状态(Blocked-suspend)

就绪挂起状态(Ready-suspend)

挂起有以下几种情况:

阻塞到阻塞挂起

就绪到就绪挂起

运行到就绪挂起

就绪挂起到就绪

阻塞挂起到阻塞

阻塞挂起到就绪挂起(在外存时的状态转换)

 

进程调度

多道程序设计的目的是无论何时都有进程在运行,从而使 CPU 利用率达到最大化。分 时系统的目的是在进程之间快速切换 CPU 以便用户在程序运行时能与其进行交互。为了达 到此目的,进程调度选择一个可用的进程(可能从多个可用进程集合中选择)到 CPU 上执 行。单处理器系统从不会有超过一个进程在运行。如果有多个进程,那么余下的则需要等 待 CPU空闲并重新调度。

调度程序的目的就是合理地选择/执行I/O为主和CPU为主的进程。

上下文切换(context switch)

将 CPU 切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态,这一任务称为上下文切换 (context switch)。当发生上下文切换时,内核会将旧进程的状态保存在其 PCB 中,然后装入经调度要执行的并己保存的新进程的上下文。

 

进程间通讯

如果一个进程不能影响其他进程或被其他进程影响,那么它是独立的,否则是协作的。

提供环境允许进程协作的理由如下:

信息共享( infonnation sharing): 由于多个用户可能对同样的信息感兴趣(例如共享的文件),所以必须提供环境以允许对这些信息进行并发访问

提高运算速度( computation speedup): 如果希望一个特定任务快速运行,那么必须 将它分成子任务,每个子任务可以与其他子任务并行执行。注意,如果要实现这样的加速, 需要计算机有多个处理单元(例如CPU 或 va 通道)。

模块化( modularity) :可能需要按模块化方式构造系统,如第2 章所讨论,可将系 统功能分成独立进程或线程。

方便 (convenience):单个用户也可能同时执行许多任务。例如,一个用户可以并行进行编辑、打印和编译操作。

 

共享内存系统(Shared Memory System)

采用共享内存的进程间通信需要通信进程建立共享内存区域。

通常,一块共享内存区域驻留在生成共享内存段进程的地址空间。其他希望使用这个共享内存段进行通信的进程必须将此放到它们自己的地址空间上。回忆一下,通常操作系统试图阻止一个进程访问另一进程的内存。共享内存需要两个或更多的进程取消这个限制,它们通过在共享区域内读或写来交换信息。数据的形式或位置取决于这些进程而不是受控于操作系统。进程还负责保证它们不向同一区域同时写数据。‘’

 

通信机制

协作进程需要一种进程间通信机制 (interprocess communication, IPC) 来允许进程相互 交换数据与信息。进程间通信有两种基本模式: (1)共享内存, (2) 消息传递。在共享内存模式中,建立起一块供协作进程共享的内存区域,进程通过向此共享区域读或写入数据

来交换信息。在消息传递模式中,通过在协作进程间交换消息来实现通信。下图给出了 这两种模式的对比。

在操作系统中,上述两种模式都很常用,而且许多系统也实现了这两种模式。消息传

递对于交换较少数量的数据很有用,因为不需要避免冲突。对于计算机间的通信,消息传

递也比共享内存更易于实现。共享内存允许以最快的速度进行方便的通信,在计算机中它

可以达到内存的速度。共享内存比消息传递快,消息传递系统通常用系统调用来实现,因

此需要更多的内核介入的时间消耗。与此相反,在共享内存系统中,仅在建立共享内存区

域时需要系统调用,一旦建立了共享内存,所有的访问都被处理为常规的内存访问,不需

要来自内核的帮助。

猜你喜欢

转载自blog.csdn.net/qq_37205708/article/details/86518038