1、摘要
本文将重点介绍对进程的个人理解,分别从概念、作用、组成、原理、实现、通信进行介绍。
2、进程的概念
进程定义为:
一个其中运行着一个或多个线程的地址空间和这些线程所需要的系统资源。(UNIX标准)
从定义中可以做出个人解释:
首先,进程是一个可执行程序,但是进程并不代表就是linux操作系统中一个a.out(编译出来的一个可执行程序)。因为可能在一个a.out中会定义一个父子进程(后面会解释)。
其次,进程有自己的地址空间、内存、数据栈。这就说明在内存中是有一块地址是属于进程的,该地址用于存放进程所需要的资源(比如变量等相关数据)。
从个人理解来看,进程的直观体现就是一个程序的执行,当我创建两个进程,就代表可以同时执行两个操作,这两个操作是并行的(同时的)。
2.1 进程和程序的关系
一个程序可以对应多个进程
程序 |
静态的 |
硬盘 |
进程 |
动态的 |
内存 |
程序加载到内存(运行起来)产生进程。
3、进程的作用
进程有什么作用?
在Linux操作环境中,进程和信号构成了它的基础部分。这两个控制指着Linux执行的几乎所有活动。
所以从个人角度理解:进程和线程是操作系统的一个特点。众所周知程序是按顺序执行的,那么就不可能同时执行多个操作,但是进程可以并行多个操作。(这里说的同时并非是同时执行的,而是与进程调度有关系,下面会进行解释)。因此进程可以算是并行任务的基本单位。
4、进程的组成
进程的组成是由:进程管理块(PCB)、各个段(栈、堆、bss、data、text)组成的。
进程都是独立的,但共用一个内核,内核里面存储了PCB块,用来记录和控制进程的信息(PCB1,PCB2,PCB3)。
具体的组成可以参照下面的图片。
由上图可知,一个进程:3G+内核1G。如果开辟两个进程:3G+3G+1G = 7G。
PCB控制块里存放了进程的相关属性,内核可以通过进程调度器来获取进程属性,进而控制进程的执行等相关操作。
5、进程原理
原理将从进程状态、进程调度、进程优先级三个部分进行解释,下面的内容都是基于多个进程进行说明的
5.1 进程状态
在一个程序中,进程有三个状态(三态模型):就绪态、执行态、阻塞态,这三个状态代表着进程的生命周期。而控制这三个状态的转换,是由在内核中PCB(进程控制块)决定的。
5.2 进程调度和进程优先级
先解释三个名词:进程调度、时间片、进程优先级
5.2.1 什么是进程调度?
Linux操作系统为了更好的管理进程,防止进程之间互相冲突竞争,Linux内核用进程调度器来决定进程执行顺序。
5.2.2 什么是时间片?
时间片是指操作系统分配给每个进程的时间段。在多任务系统中,CPU会轮流执行各个进程,每个进程被分配一个小的时间片用于执行。当一个进程的时间片用完时,操作系统会暂停该进程的执行,保存当前状态,然后切换到下一个进程的时间片。
在进行切换任务时,那么如何记录执行到一半的数据呢?进程用来记录程序执行的状态,描述和记录正在运行中的程序的相关信息,进程是动态的。
5.2.3 什么是进程优先级?
进程调度分配给每个进程的时间片频率是根据进程的优先级来判定的,优先级越高,所分到的时间片执行次数就越多,那么该进程运行的更加频繁。操作系统根据nice值来决定进程的优先级,nice值越大,优先级越低。
在一台单处理计算机上,同一时间只能有一个进程可以运行,其他进程处于等待运行状态(就绪态),每个进程的时间片是相当短暂的,所以从直观体现上就是多个程序在同时运行的假象。
5.2.4 对三态模型解释
因此再对上面的三态模型进行解释:有两个进程,分别为进程1和进程2,假设进程1的优先级为0,进程2的优先级为1。进程调度会按照优先级分配给进程1更多的时间片,进程1从就绪态变为执行态,当时间片耗尽的时候切换成就绪态,进程2由就绪态变为执行态,当进程2在分配到时间片的这段时间里遇到一个需要阻塞的操作,就切换到阻塞态,阻塞操作结束后又切换到就绪态。
总体来说:
Linux内核通过进程调度器获取PCB块属性,根据进程的优先级来确定执行的频率,优先级越高执行频率越高。操作系统通过轮流调度各个进程的执行,实现了多个任务在同一时间段内的交替执行,从而给用户带来了多任务操作的错觉。所以有一句话叫宏观并行,微观串行
6、进程实现
6.1 创建进程
在Linux操作系统中,在同一个.c文件中使用fork()创建进程。
pid_t fork();
该函数会返回两次pid
当pid > 0 表示父进程
pid = 0 表示子进程
对该函数做以下说明:
通过该函数可以从当前进程中克隆一个同名新进程。因为本身main函数本身就是一个进程,所以fork之后会产生另一个进程叫子进程,原有的叫父进程
子进程是父进程的完全拷贝,子进程的执行过程是从fork函数之后执行,子进程与父进程具有相同的代码逻辑。
一次调用,会返回两次。父子进程谁先运行顺序不确定。
变量不共享,子进程复制父进程的0到3g空间和父进程内核中的PCB,但pid号不同。
6.1.1 父与子进程运行期间的关系是什么?
子进程会继承父进程的大部分资源(除去if判断pid号里面的内容,其他内容都是一样的),子进程和父进程独立执行任务。
6.2 收尸操作(阻塞操作)
功能:
父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
#include <sys/wait.h>
pid_t wait(int *status);
参数:
status为输出型参数,获取子进程退出状态
不关心则可以设置成NULL
返回值:
成功返回被等待进程的pid
失败返回-1.
僵尸进程:子进程结束,父进程没有收尸 。
孤儿进程 :子进程还在,父进程不在。
如果有多个子进程exit退出,变成僵尸态,在使用wait或者waitpid收尸的时候,分不清谁是哪个进程,wait一次性只能收尸一个,他的执行流程是:只要有一个子进程死掉被wait检测到了,就把他收尸,收尸的过程中不管他是哪个进程,然后把他死掉的原因记录到一个变量中,如果想要获取他死掉的原因,需要使用宏定义进行读取。
父进程和子进程的执行顺序,子进程和父进程执行顺序是由操作系统调度的,不清楚是谁先运行的,如果父进程先运行完就结束,子进程变成孤儿;所以在使用父子进程的时候,尽量使用wait阻塞操作。
6.3 其他操作
关于进程的实现和终止还有很多函数,这里不做过多说明。不过可以罗列出终止的几类方式
进程的终止(阻塞):
main中return
exit()
_exit()
主线程退出
主线程调用pthread_exit
abort()//发送一个SIGABRT
Signal //发信号,结束了进程 kill id -9
最后一个线程被pthread_cancle
7、进程通信
关于通信部分不进行过细的介绍,只做一些简单的原理描述。
7.1 进程之间为什么需要通信?
因为进程之间需要进行数据交互、共享资源、通信实现某些功能
7.2 进程之间的通信方式
同一个主机:有名管道 无名管道 共享内存 信号量集
有名管道(pipe):只能给同一个.c下的进程通信(父子进程)
无名管道(fifo):可以给任意单机进程通信(.c文件之间通信)
共享内存(shm):共享内存是不阻塞,下一次数据的输入会把之前的数据给覆盖掉。
不同主机:
套接字(socket)
7.2.1 管道通信原理
进程之间有公共内存空间,可以创建一个管道用来进程或文件之间的通信操作,但是管道之间的传输数据是单向的。只能一端是读,一端是写,如果想要两端都可以读写,则需要创建两个管道来实现。管道读的过程是,管道里有内容,进程读一个少一个,比如父进程写fd[0]一个helloworld
,如果子进程fd[1]读,那读一个字符管道就少一个字符
进程之间有公共内存空间,那么可以在公共空间创建一个管道,用来进行数据交换。
无名管道------>pipe ------->只能给有亲缘关系进程通信(父子进程)
有名管道 ------>fifo -------->可以给任意单机进程通信(.c文件之间通信)
7.2.2 管道通信的整体框架
创建管道 :类似文件 (管道文件)
打开管道 :open
读写管道 :read/write
关闭管道 :close
7.2.3 共享内存原理
共享内存方式是进程通信中效率最高的方式,其原理如下图所示。
与管道方式不同,共享内存是不阻塞的,共享内存空间有什么就输出什么,如果没有就输出NUL。
读取方式与管道方式也不同:IPC读取方式是数据在共享内存存在的,直到下一次数据的输入会把之前的数据给覆盖掉。
7.2.4 共享内存的整体框架:
创建共享内存段
ftok 创建IPC key
shmget 创建共享内存空间
连接共享内存空间
shmat连接
断开共享内存空间
shmdt断开
删除共享空间
shmctl删除
7、总结
上面是我对进程的个人理解,只是针对Linux操作系统进程的简单使用,没有进行更深入的研究。参考书籍:
《Linux程序设计第4版》-----好书,强推,里面有对进程的详细介绍,包括通信。
《Linux网络编程》------这本书里面也有对进程的介绍,也很详细