目录
- 基本概念
- 进程结构
- 进程基本操作
基本概念-进程简介
什么是进程?
进程的概念首先是在20世纪60年代初期有 MIT 的Multics 分时系统和IBM的 TSS/360系统引入的,在40多年的发展中,人们对进程有过各种各样的定义,列举较为著名的几种。
进程是一个独立的可调度的活动(Cohen, Jofferson);
进程是一个抽象的实体,当它执行某个任务时,要分配和释放各种资源。
(P.Denning)
进程是可以并行执行的计算单位(Madnick,Donovan)
本质:进程是程序的一次执行的过程,进程是程序执行和资源管理的最小单元。 在Linux环境下,每个正在运行的程序都称为进程
基本概念-进程控制块
什么是进程控制块(PCB)?
进程是Linux 系统中调度和管理资源的单位,那系统如何描述和表示一个进程的变化呢,答案是通过进程控制块来实现的。
进程控制块包含了那些内容呢?
进程的描述信息(进程ID,进程状态…);
进程控制信息;(调度信息,时间片,优先级…)
进程的资源信息(内存资源,打开的文件,使用的定时器…)
Linux 系统中进程控制块中的每一项都是通过 task_struct 结构体来表示,定义在 #include <linux/sched.h >头文件中的。
基本概念-进程标识
既然进程是Linux 系统中基本的调度和管理资源的单位,那系统就需要能够唯一标识出每一个进程,才能更好的进行调度和管理的工作,Linux 系统是通过进程号(PID)来唯一的标识一个进程,
父进程标识号:当前进程的父进程的标识,(PPID)
PID 和 PPID 都是非零的正整数。
进程的特性
- 动态性
- 进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
- 并发性
- 多个进程可以同一时间运行在一个内存空间中,任何进程都可以同其他进程并发执行。
- 独立性
- 虽然在一个内存空间中有多个进程在运行,但其实每个进程都运行在各自的虚拟内存空间中,互不干扰。
- 异步性
- 由于进程间的相互制约,使得进程具有执行的间断性,也就是说,进程按各自独立的,不可预知的速度向前推进。
- 结构特性
- 每个进程都有自己的私有空间,在这个私有空间中,会涉及四个不同的段落,进程在内存中的结构由代码段,数据段,堆段和栈段四部分组成。
进程结构
- Linux 是一个多进程的系统,进程之间具有并发性,互不干扰,也就是说,每个进程拥有各自的权利和义务,其中,各个进程都运行在独立的虚拟地址空间,因此,即便是一个进程发生异常,也不会影响到系统中其他的进程。
- Linux 系统的进程主要包含4个段,分别为 “代码段”,“数据段”,“堆段”,“栈段”
- 代码段:存放程序代码
- 数据段:存放数据
- 栈段: 存放临时变量
- 堆段: 存放动态申请变量
- BSS 数据段:没有初始化的全局变量
- 只读数据段:初始化的全局变量,静态变量,常量
进程模式和类型
- Linux 系统中,进程执行模式分为用户模式和内核模式,如果当前运行的是用户程序,应用程序或者非内核的系统程序,那么进程就运行在用户模式下,如果用户程序在执行过程中出现系统调用或者发生中断事件,那么就要运行系统程序,进程模式就变为内核模式,内核模式下的进程可以执行机器的特权指令,此时运行的进程不受到用户的干扰,即使是 root 用户也不能干扰内核模式下进程的运行。
进程的状态
进程有4种基本状态:
-
运行状态 (RUNNING)
- 进程正在执行。
-
就绪 (Ready)
- 进程已经获取到除处理器外的所有资源,等待分配处理器资源,只要分配到处理器资源就可以运行。一般就绪进程是会有一个就绪进程队列,有系统来调度。
-
等待(阻塞Blocked)
- 进程因等待某个事件发生而暂停运行。
-
停止 (stop)
- 进程已经完成任务。
进程的状态转化
- 运行----> 等待
- 正在运行的进程因等待某事件发生,转入等待状态。
- 等待—>就绪
- 处于等待状态的进程,因所等待的事件发生了,进入就绪对队列让系统调度运行。
进程与程序
进程不等同于程序。程序是一些存在磁盘上的一个没有生命的指令集合,是个静态的概念,没有任何执行的概念,而进程是动态的概念。它是程序执行的过程,包括了动态创建、调度和消亡的整个过程。进程是程序执行和资源管理的最小单位。
对系统而言,当用户在各级系统中键入命令执行一个程序的时候,它将启动一个进程,因此,一个程序可以对应多个进程。
进程的基本操作
进程创建 |
---|
pid_t fork(void); |
pid_t vfork(void); |
获取进程ID |
uid_t getpid(void); |
uid_t getppid(void); |
进程等待 |
pid_t wait(int *status); |
pid_t waitpid(pid_t pid,int *status, int options); |
进程退出 |
void exit(int status); |
void _exit(int status); |
进程运行新程序 |
---|
int execl(const char *path,const char *arg,….); int execlp(const char *file,const char *arg,….); int execle(const char *path,const char *arg,….,char *const envp[ ]); |
int execv(const char *path, char *const argv[ ]); int execvp(const char *file, char *const argv[ ]); int execve(const char *filename, char *const argv[ ],char *const envp[ ]); |
1.进程创建
函数名 | fork |
---|---|
头文件 | #include <unistd.h> #include <sys/types.h> |
函数原型 | pid_t fork(void); |
功能 | 创建子进程 |
参数说明 | 无 |
返回值 | fork 函数的奇妙之处在于调用一次,返回两次,可能有3种不同的返回值: 父进程中,fork返回新创建的子进程的PID 子进程中,fork返回0 出错:返回 -1,并将错误码存入 errno 中 |
fork函数
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,一个进程调用fork()函数后,就会创建一个新的进程,系统先给新的进程分配资源,然后把原来的进程的所有值都复制到新的新进程中,包括数据空间、堆、栈和进程描述符等,只有少数值与原来的进程的值不同(比如原进程的定时器,操作信号)。相当于克隆了一个自己,子进程从fork函数的返回值处开始执行。
创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
函数名 | vfork |
---|---|
头文件 | #include <unistd.h> #include <sys/types.h> |
函数原型 | pid_t vfork(void); |
功能 | 创建子进程 |
参数说明 | 无 |
返回值 | 父进程中,fork返回新创建的子进程的PID 子进程中,fork返回0 出错:返回 -1,并将错误码存入 errno 中 |
与fork的区别 | 1.fork: 子进程拷贝父进程的数据段,代码段 vfork: 子进程与父进程共享数据段和堆栈空间,所以子进程对变量修改和父进程会同步。详细的查看手册 2.fork: 子进程和父进程运行次序不固定,由系统调度决定 vfork: 子进程会先于父进程运行 |
2.进程退出
函数名 | exit和_exit |
---|---|
头文件 | #include <unistd.h> #include <stdlib.h> |
函数原型 | void exit(int status); void _exit(int status); |
功能 | 退出,或终止进程 |
参数说明 | status:退出码/退出的状态值 |
返回值 | 无 |
两者之间的差别 | exit相对于_exit 会多一个缓冲区清理的工作 |
exit 和return 区别
void Test()
{
printf(“Hello\n”);
return ;
}
void Test()
{
printf(“Hello\n”);
exit(EXIT_SUCCESS) ;
}
int main()
{
while(1)
{
Test();
sleep(1);
}
return 0;
}
上述代码中:exit退出整个进程,return 退出当前函数
3.获取进程ID
函数名 | getpid,getppid |
---|---|
头文件 | #include <unistd.h> #include <sys/types.h> |
函数原型 | pid_t getpid(void); pid_t getppid(void); |
功能 | getpid:获取当前进程ID getppid:获取当前进程父进程ID |
参数说明 | 无 |
返回值 | getpid:返回当前进程ID getppid:返回当前进程父进程ID 注意:该两个函数调用不会出错。 |
4.进程等待
函数名 | wait |
---|---|
头文件 | #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> |
函数原型 | pid_t wait(int *status); |
功能 | 阻塞父进程,直到某个子进程退出 |
参数说明 | status :获取到的子进程结束状态值,获取到status后用 WEXITSTATUS(status) 来转换子进程退出时的退出码 |
返回值 | 成功:返回退出的子进程的ID 错误:返回 -1 说明:wait 是waitpid 的一个特例 |
函数名 | waitpid |
---|---|
头文件 | #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> |
函数原型 | pid_t waitpid(pid_t pid, int *status, int options); |
功能 | 阻塞调用进程,直到某个子进程退出 |
参数说明 | Pid: 等待的子进程ID; status :获取到的子进程结束状态值; options:附加选项 (详见 手册说明) |
返回值 | 成功:返回退出的子进程的ID 错误:返回 -1 |
5.进程运行新程序
exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。
一个进程调用exec族函数后,代码段,数据段,堆段,栈段的都会被新程序覆盖,唯一保留的是PID(进程号)
函数名与参数的关系:
细看一下,这6个函数都是以exec开头(表示属于exec函数组),前3个函数接着字母l的,后3个接着字母v的,我的理解是l表示list(列举参数),v表示vector(参数向量表)
字母p是指在环境变量PATH的目录里去查找要执行的可执行文件。2个以p结尾的函数execlp和execvp,看起来,和execl与execv的差别很小,事实也如此,它们的区别从第一个参数名可以看出:除 execlp和execvp之外的4个函数都要求,它们的第1个参数path必须是一个完整的路径,如"/bin/ls";而execlp和execvp 的第1个参数file可以仅仅只是一个文件名,如"ls",这两个函数可以自动到环境变量PATH指定的目录里去查找。
字母e是指给可执行文件指定环境变量。在全部6个函数中,只有execle和execve使用了char *envp[]传递环境变量,其它的4个函数都没有这个参数,这并不意味着它们不传递环境变量,这4个函数将把默认的环境变量不做任何修改地传给被执行的应用程序。而execle和execve用指定的环境变量去替代默认的那些。
函数名 | execl |
---|---|
头文件 | #include <unistd.h> |
函数原型 | int execl(const char *path,const char *arg,….); int execlp(const char *file,const char *arg,….); int execle(const char *path,const char *arg,….,char *const envp[ ]); |
功能 | 用path或者file所指定的进程地址空间替换调用进程的地址空间 |
参数说明 | Path: 执行程序的全路径; file :执行程序的名称; arg…:被执行程序所需的命令行参数,含程序名。以空指针结束 . |
返回值 | -1:错误发生 并记录错误码 errno注意:该函数只有在错误发生的时候才返回。 |
函数名 | execv |
---|---|
头文件 | #include <unistd.h> |
函数原型 | int execv(const char *path, char *const argv[ ]); int execvp(const char *file, char *const argv[ ]); int execve(const char *path, char *const argv[ ],char *const envp[ ]); |
功能 | 用path或者file所指定的进程地址空间替换调用进程的地址空间 |
参数说明 | Path: 执行程序的全路径; file :执行程序的名称; argv :被执行程序所需的 |
返回值 | -1:错误发生 并记录错误码 errno注意:该函数只有在错误发生的时候才返回。 |