注:该文章提及的函数请查看man手册来查找具体用法、头文件与返回值,文章里只描述部分功能
一、进程概述
程序是有序指令的合集,而程序的执行过程就是进程
进程是一个独立的可调度的活动,由操作系统进行统一调度,相应的任务会被调度到 cpu 中进行执行。
进程一旦产生,则需要分配相关资源,同时进程是资源分配的最小单位
作用:可以使多个任务同时进行,提高cpu运行效率。
二、进程的内存分配和虚拟地址
Linux在程序运行后为进程分配的空间如下图(Linux系统分配的4g内存是虚拟地址):
栈 的特点是先进后出 (FILO) ,可以用来存储函数的调用栈、内存的分配操作、表达式求值的临时变量以及与程序中的控制流相关的数据,还是一种高效的内存结构,用于存放基础数据类型和引用类型的变量,大大简化内存的管理,提高了程序的执行效率。
堆 则为程序提供了极为灵活的空间分配和管理手段,既可以手动管理,也可以交由垃圾回收机制自动管理,带来了很大的方便。
进程的4g虚拟空间可以通过下图理解:
虚拟地址:并不是一个真实的内存地址,而是一个寻址的编号
物理地址:是真实的内存地址
物理地址和虚拟地址通过cpu的硬件内存管理单元MMU来实现虚拟内存和物理内存的映射
(操作系统可以设置 MMU 中的映射内存段)
linux系统运用虚拟地址的作用:
1.直接访问物理地址,会导致地址空间没有隔离,很容易导致数据被修改
2.通过虚拟地址空间可以实现每个进程空间都是独立的,操作系统会映射到不用的物理地址区间,在
访问时互不干扰.
三、进程的相关命令
ps:显示进程的相关信息
显示所有进程的详细信息:ps -aux
与-aux相似,只是信息要少一些:ps -ef
可以配合管道符使用:例:使用 ps -ef | grep “可执行文件名” 查找进程
top:实时显示进程的信息
常用选项:
[ -i ] 不显示任何闲置 (idle) 或无用 (zombie) 的进程
[ -n 数字 ] 更数指定次数后,退出top命令
pstree:将所有的进程以树型结构的方式进行展示
kill 命令是用于结束进程的命令或者用于显示相关信号
结束进程:kill -9 进程id
四、并行与并发
并行:硬件上增加cpu的内核,增加cpu的核心数,这样cpu可以在不同的内核上同时执行程序
并发:在同一时间段有多个任务在同时执行,由操作系统调度算法来实现,比较典型的就是时间片轮转(如下图)
解释:我们给时间分成很短暂的时间片,由于每段时间cpu都只能执行一个任务,那么我们在每段时间片后切换执行的任务,这样在宏观感知上就像是在同时在执行几个任务。
五、进程的状态周期(生命周期)
进程是程序的执行过程,即程序从建立、运行到终止释放的过程,这样一个循环就是进程的生命周期
在三态模型中,进程状态分为三个基本状态,即运行态,就绪态,阻塞态。
运行(running)态:进程占有处理器正在运行。
就绪(ready)态:进程具备运行条件,等待系统分配处理器以便运行。
等待(wait)态:又称为阻塞(blocked)态或睡眠(sleep)态,指进程不具备运行条件,正在等待某个事件的完成。
三态模型如下图:
在五态模型中,进程分为新建态、终止态,运行态,就绪态,阻塞态
NULL一一>新建态:执行1个程序,创建一个子进程。
新建态一一>就绪态:当操作系统完成了进程创建的必要操作,并且当前系统的性能和内存的容
量均允许。
运行态一一>终止态:当1个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系
统所终结,或是被其他有终止权的进程所终结。
终止态一一>NULL:完成善后操作。
常用的状态:
1.运行态(TASK_RUNNING) : 此时进程或者正在运行,或者准备运行,就绪或者正在进行都属于运行态
2.睡眠态(TASK_SLEEP): 此时进程在等待一个事件的发生或某种系统资源
1) 可中断的睡眠(TASK_INTERRUPT) : 可以被信号唤醒或者等待事件或者资源就绪
2) 不可中断的睡眠(TASK_UNTERRUPT) : 只能等待特定的事件或者资源就绪
3.停止态(TASK_STOPPED) : 进程暂停接受某种处理。例如:gdb调试断点信息处理。
4.僵尸态(TASK_ZOMBIE):进程已经结束但是还没有释放进程资源
六、Linux的进程管理
Linux的进程管理系统是树形结构,每个进程都要有它的父进程
(通过pstree命令可以将所有进程都以树形结构显示出来)
而每个进程都会被分配一个唯一的标识符,即为进程id
获取进程id的函数:
pid_t getpid(void);(返回当前进程的id)
pid_t getppid(void);(返回当前进程的父进程id)
pid_t 实际上是 int 类型
-------------------------------------------------以下章节是进程的具体实现-------------------------------------------------
七、创建进程与多个子进程
1.用fork()函数来创建子进程
2.创建的进程都只能由父进程来管理,不允许进行递归创建,即子进程里不允许创建进程
3.fork函数往下的程序就是子进程的程序,但也是父进程的程序
4.如果要对子进程或父进程添加独立的程序,就要使用fork()的返回值来进行
5.父子进程的执行顺序由操作系统算法决定的,不是由程序本身决定
6.子进程会拷贝父进程地址空间的内容,包括缓冲区、文件描述符等
(对文件操作时文件描述符是作为操作文件的唯一标识,也就是说文件操作时的文件偏移量和文件状态都被子进程复制了)
fork函数:
函数原型:pid_t fork(void);
返回值:
成功:返回给父进程是子进程的pid,返回给子进程的是0
失败:返回-1,并设置errno
演示父子进程的使用并添加独立的程序:例:
代码运行结果:
如上可知:父子都打印了hello
并且可发现问题:hello为什么有时会打印在命令输入行上?
这是因为当父进程比子进程先结束,所以父进程完成,界面回到命令输入之后子进程才打印。
创建多个子程序:
代码运行结果:
七、进程的退出(exit函数与_exit函数)
在进程结束时,需要释放分配给进程的地址空间以及内核中产生的各种数据结构,才能真正退出
exit与_exit函数就能实现上述功能
exit
原型:void exit(int status);
参数status是退出的状态,可以自己设定,而linux系统中有两个已设定的状态值:
EXIT_SUCCESS 正常退出,EXIT_FAILURE 异常退出,具体定义在stdlib.h 中
#define EXIT_FAILURE 1 // Failing exit status.
#define EXIT_SUCCESS 0 // Successful exit status.
在退出的时候还会刷新缓冲区
_exit
参数与返回值都与exit一样,大体功能也相似,但是并不会刷新缓冲区
两者还有一个区别,exit是库函数中的,属于用户空间,而_exit是系统调用,是内核空间
exit是基于_exit函数实现的
八、进程的等待(wait,waitpid)
子进程在结束退出(使用exit或_exit)的时候会进入僵死状态,其在内核中的数据结构还保留着
这时父进程调用就可以等待子进程结束并且释放这些保留的资源
wait
父进程阻塞等待子进程退出并释放子进程遗留资源
原型:pid_t wait(int *wstatus);
参数 wstatus的作用是保存子进程的退出状态,通过调用系统内部定义的宏函数可以查看退出的子进程的各种状态(宏函数及使用可以通过Man手册查询)
返回值:退出的子程序的pid(进程号)
waitpid
作用与wait相似但比它更强大,可理解为wait底层调用waitpid
原型:pid_t waitpid(pid_t pid, int *wstatus, int options);
参数
pid:进程id
-1 可以等待任意子进程
>0 等待id为pid的进程
wstatus:保存子进程退出状态值的指针
options选项:WNOHANG —非阻塞选项;0 —阻塞式与wait等同
函数返回值
>0 退出进程的pid
=0 在非阻塞模式下,没有进程退出
-1 调用失败,并设置errno
waitpid使用阻塞的方式等待任意子进程退出: waitpid(-1,&status,0);
waitpid使用非阻塞的方式等待子进程退出: while((cpid=waitpid(-1,&status,WNOHANG))==0);
九、进程替换(execl函数族)
:创建的pid进程在内核的信息不变,替换到别的代码运行
通过execl函数族实现
最基本的execl函数:
原型:int execl(const char *pathname, const char arg, …/ (char *) NULL */);
用法如下图:主进程替换到 /bin/ls文件来运行,参数列表为:“ls” “-l” (NULL为结束标志)
其他的execl函数族函数如下:
int execlp(const char *file, const char arg, …/ (char *) NULL */);
int execle(const char *pathname, const char arg, …/, (char *) NULL, char
*const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
参数:
path:可执行文件的路径名
file:可执行文件名,可以通过PATH环境变量指定的路径
arg:参数列表,以NULL结尾
argv[]:参数数组
envp[]:环境变量数组
例:
使用execlp实现同上功能如下图:
execv:
结尾:linux进程的具体实现先到这,如要了解进程之间的通信请进入本人主页查看后面的文章