进程的定义
- 从用户的角度来看,进程是一个程序的
动态执行
过程。是一个把静态程序文件加载进内存、分配资源、执行、调度和消亡的过程。- 从操作系统的角度来看,进程是
资源分配的基本单位
。进程需要占用CPU资源
以执行程序指令;而除了需要占用CPU资源
以外,进程还需要占据存储资源
来保存状态。进程需要保存的内容包括数据段、代码段、堆、栈
以及其他内存空间
,进程也需要占用资源管理打开的文件、挂起的信号、内核内部数据、处理器状态、存在内存映射的内存空间以及执行线程,而执行线程的信息则包含程序计数器、栈和寄存器状态。
常用函数
fork()
创建子进程
pid_t pid = fork(); if(pid == 0){//子进程 }else{//父进程 } 简便方法: if(fork()==0){ }else{ }
父子进程对同一个文件不会共享读写位置。
exec
exec是一系列的系统调用。它们通常适用于在fork之后,用于在当前进程的上下文中加载并运行一个新的程序。
当进程执行到exec系统调用的时候,它会将传入的指令来取代进程本身的代码段、 数据段、栈和堆,然后将PC指针重置为新的代码段的入口。 但进程的 PID 保持不变。
execl()
定义: int execl(const char *path, const char *arg0, ... /*, (char *)0 */); 用法: execl("/home/snow/code/test/11_process/05_test", "./05_test", "1", "2",(char *)0); // path参数表示可执行文件路径 // 函数名当中的l表示列表list的含义,它要求传入可变数量的参数,并且每个参数对应一个命令行参数,最后以0结尾 // 如果执行成功,它不会返回到原来的程序,因为原程序的执行内容已被新程序替换。如果有返回值意味着出现了错误,将返回-1
execv()
定义: int execv(const char *path, char *const argv[]); 用法: execv("/home/snow/code/test/11_process/05_test", args); // 函数名当中的v表示向量vector的含义,它要求传入一个指针数组,数组中的每个元素指向同一个字符串的不同位置
函数原型
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 execvpe(const char *file, char *const argv[],char *const envp[]); //path:可执行文件的路径名字 //arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束 //file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。 //返回值:exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。 //p:使用文件名,并从PATH环境进行寻找可执行文件 //v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。 //e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
ps:实际上,system函数以及从bash或者是其他shell启动进程的本质就是fork+exec
exit
_Exit
、_exit
和exit
都是用于立即终止进程的函数,但它们在处理进程终止时的行为上有所不同:
- 当调用
_exit
和_Exit
的时候,进程会直接终止返回内核,- 而
exit
函数则会首先执行终止处理程序,然后清理标准IO (就是把所有打开的流执行一次fclose ),最后再终止进程回到内核。在进程阶段,进程总共有5种终止方式,其中3种是正常终止,还有2种是异常终止:
终止方式 终止情况 main函数中调用return 正常 调用exit函数 正常 调用 _Exit
函数或者_exit
函数正常 调用abort函数 异常 接收到引起进程终止的信号 异常
进程控制
- 孤儿进程(没啥危害)
- 子进程还存在,父进程就退出了,此时子进程自动被PID为1的进程收养。
- 僵尸进程(有危害)
- 子进程退出但是父进程未调用wait或waitpid函数进行清理,则仍占用一个进程表条目,若产生大量僵尸进程,进程表耗尽,则无法创建新进程。
wait()
- 随机地等待一个已经退出的子进程,返回该子进程的PID,并进行清理回收子进程资源工作。
- wait是一个阻塞函数, wait会一直阻塞直到等到一个结束的子进程, 解除阻塞.(前提是有子进程,没有子进程的时候, 直接返回-1)
- wait(int *wstatus);参数用于存储进程的退出状态,若不关心可以为NULL
waitpid()
指名道姓的wait()
pid_t waitpid( pid_t pid, // 指定等待的PID的子进程 int *wstatus, // 存储进程的退出状态 int options // 修改 waitpid 的行为的选项 );
pid参数:
- pid参数可以控制支持更多种模式的等待方式。
PID数值 效果 < -1 等待进程PID和pid绝对值的进程 == -1 等待任一个子进程, 等价于wait() == 0 等待同一进程组的任意子进程 > 0 等待指定PID的进程(此为正常情况,即直接写pid)
守护进程
用户组与权限
- 进程在运行过程中必须具有用户身份,以便于内核进行进程的权限控制。在默认情况下,程序进程拥有启动用户的身份。
- 假设当前登录用户为A,他运行了任意一个程序, 无论是不是他创建的,则程序在运行过程中就具有A的身份,该进程的
用户ID
和组ID
分别为A
和A所属的组
,其ID和组ID就被称为进程的真实用户ID和真实组ID。- 真实用户ID和真实组ID可以通过函数getuid()和getgid()获得。
进程除了真实用户ID和真实组ID以外, 进程还具有有效用户ID和有效组ID的概念。
- 当系统内核对进程的访问权限检查时,它检查的是进程的有效用户ID和有效组ID,而不是真实用户ID和真实组ID。
- 默认情况下,用户的有效用户ID和真实用户ID是相同的,有效组ID和真实组ID是相同的。
- 有效用户ID和 有效组ID通过函数geteuid()和getegid()获得。
ps: 拓展
// 更改文件或目录的所有者和所属组 sudo chown root:root 文件 // 增加用户s权限 sudo chmod u+s 文件
- 守护进程
- 一直运行在后台,如日志记录,系统监控
- 守护进程的创建流程
- 父进程创建子进程,然后让父进程终止。
- 在子进程当中创建新会话。
- 修改当前工作目录为根目录。
- 重设文件权限掩码为0,避免创建文件的权限受限。
- 关闭不需要的文件描述符,比如0、1、2。