1. 进程与程序
进程是程序执行的实例,程序包含一系列信息描述如何创建进程。系统会为进程分配资源,进程是系统调度资源的基本单位。
2. 执行进程
2.1 并发与并行
单个CPU系统,微观上任一时刻只有一个程序运行。系统为每个进程分配时间片(CPU时间),多个进程轮流使用CPU资源。
并发:多个进程被快速轮换执行
并行:多个进程在多个处理器上同时执行
2.2 进程控制块
系统管理进程时需要记录进程的某些信息,这些信息保存在内核区的进程控制块中(PCB)。
进程控制块实现为结构体,其中常见信息包括:进程id、进程状态、文件描述符表、信号集、进程切换保留的寄存器状态等。
2. 3 进程状态
就绪态:得到CPU资源后就能正常工作
运行态:正在占用CPU资源
阻塞态:即使得到CPU资源也不能正常工作
3. 进程创建
3.1 虚拟地址空间
程序直接使用真实物理地址会存在空间不隔离、地址不确定、使用效率低等问题,因此引入虚拟地址空间,每个进程的虚拟地址空间地址都从0开始,进程角度看仿佛占用所有内存资源,且地址使用确定且方便。虚拟地址空间采用分页映射的方式,每当执行到某一页,即为其分配一内存页,并完成映射,实现空间的隔离,提高内存使用效率。
虚拟地址空间,从高位到低位分别有:代码段、数据段(已初始化、未初始化)、堆区、共享区、栈区、环境变量、内核区。
3.2 进程创建
- fork
调用api----fork创建子进程,创建成功fork将在父进程中返回子进程id、在子进程中返回0,通过判断返回值可以区分父子进程代码 - 写时拷贝
创建子进程后,并没有立刻为子进程分配物理内存,子进程的虚拟地址空间是映射到父进程的物理内存上,直到父子进程某一段数据发生改变,才为子进程分配相应的物理内存并完成映射。其中代码段直到调用exec函数族api,父子进程都是共用一个物理内存。
4. 孤儿进程与僵尸进程
4.1 回收进程资源
进程退出时,会由内核释放资源,但是内核区中的进程控制块中的某些信息会被保留,需要由父进程调用api----wait/waitpid进行回收。
4.1 孤儿进程
父进程结束运行,子进程还在运行时,子进程就成为孤儿进程。孤儿进程会被pid为1的init接管,子进程结束时由init回收资源,因此孤儿进程没有危害。
4.2 僵尸进程
子进程结束运行,父进程没有回收子进程保留的资源时,子进程就成为了僵尸进程。僵尸进程不能被kill杀死,因为进程号等资源都是有限资源,所有当出现大量僵尸进程时会造成危害。
5. 进程间通信
5.1 匿名管道
匿名管道以内核区空间作为缓冲区,调用api----pipe时产生两文件描述符,分别对应管道的读端和写端。匿名管道是半双工通信,一端只能读,一端只能写。
具有亲缘关系的进程,具有相同的文件描述符才能使用匿名管道进行通信,且管道要在使用fork之前创建。
5.2 有名管道
有名管道和匿名管道一样以内核区空间作为缓冲区,但是可以调用api----mkfifo创建管道文件,管道文件是一个特殊文件,内容存储在内存中而不是硬盘上。
进程之间可以通过指向管道文件的文件描述符进行通信,因此没有亲缘关系的进程也可以使用有名管道进行通信。
5.3 共享内存
共享内存是将物理内存的一块区域,映射到多个进程的共享区,即由多个进程共享一块内存。映射后,共享内存成为进程用户区的一部分,不需要内核区参与,因此共享内存是效率最高的进程间通信方式。
使用共享内存有创建共享内存段、挂载共享内存段、分离共享内存段、销毁共享内存段等操作。
- 创建共享内存段
调用api----shmget,用来创建一个共享内存段或者获取一个已有共享内存段的shmid,要共享同一内存段的多个进程使用shmget时,传入参数key要相同,通过相同的key可以得到相同的shmid
- 挂载共享内存段
调用api----shmat,将共享内存段映射到调用进程的共享区,返回值即为共享内存首地址(虚拟地址)。 - 分离共享内存段
调用api----shmdt,接触当前进程与共享内存段的关联 - 销毁共享内存段
调用api----shmctl,标记销毁共享内存段。多个进程调用shmctl销毁共享内存段并不会产生错误,因为只有当所有进程都与共享内存段分离后才会真的销毁。nattch即标志有几个关联进程。
5.4 内存映射
共享内存是将内存中的空间映射到多个内存,内存映射是将磁盘空间映射到内存,某一进程修改内存数据后,可以同步到磁盘空间,磁盘空间再同步到其他进程内存中。
内存映射需要使用磁盘空间,因此通常创建一文件。调用api----mmap时需要使用指向磁盘文件的文件描述符,指定空间大小一般和文件大小相同,空间的起始地址一般由内核自动给予。空间的起始地址也可以自己设定,但是需要在一页的起始,即地址需要是4k的整数倍。
5.5 信号
信号是事件发生时对进程的通知机制,对每个信号都有缺省行为,也可以注册信号捕捉,编写自定义的信号处理函数。常规信号有31个,如SIGINT、SIGCHILD、SIGPIPE、SIGKILL等,信号处理行为对程序有极大影响。调用api----sigaction可以捕捉信号,自定义信号处理行为。调用api----setitimer可以定时产生信号SIGALRM。
信号属于软中断,进入内核态后,在内核态回到用户态之前将检查可以处理的信号,如果有自定义信号处理函数,则回到用户态使用该函数,函数结束后回到内核态,再从内核态回到用户态(进入内核态前的地方)。
- 信号集
信号集用于记录一组信号的状态,数据类型为sigset_t
,占用8个字节,信号集由位图机制实现。 - 阻塞\未决信号集
在进程控制块中有未决信号集、阻塞信号集。未决信号集中置为1,表示该信号已产生,但还没有被处理;阻塞信号集中置为1,表示该阻止该信号被处理。未决信号集中置为1,阻塞信号集置为0的信号才能发生行为。
未决信号集不能被修改,但可以调用api----sigpending获取未决信号集。阻塞信号集可以被修改,通过调用api----sigprocmask,可以根据自定义信号集对阻塞信号集做阻塞、解除阻塞、替换等操作。