1、获取进程ID
每个进程都有一个唯一的正数(非零)进程ID(PID)
getpid函数返回调用进程的PID。
getppid函数返回它的父进程的PID(创建调用进程的进程)。
2、创建和终止进程
1.进程的三种状态
1. 运行
要么是正在CPU执行的,要么是正在等待被调度的
2. 停止
进程被挂起,不会被调度
当收到一些特定的信号之后,进程就会停止,直到收到另一个特殊信号之后,才再次开始运行
3. 终止
进程永远的停止了。
进程会因为三种原因终止
- 收到一个信号, 该信号的默认行为是终止进程
- 从主程序返回
- 调用exit函数。
2. 进程的终止
设置进程以状态status终止的方法有两种:
- 使用exit函数
- 在主程序中返回一个整数值
3. 进程的创建
父进程通过fork函数创建一个新的运行的子进程。
1. 子进程的创建
- 子进程得到与父进程用户级虚拟地址空间相同的一份副本(包括代码和数据段、堆、共享库以及用户栈),但是是独立的空间
- 子进程还获得与父进程任何打开文件描述符相同的副本, 这就意味着当父进程调用fork 时, 子进程可以读写父进程中打开的任何文件。
- 父进程和新创建的子进程有不同的PID。
2. fork函数的返回
fork 函数只被调用一次, 却会返回两次
一次是在调用进程(父进程)中, 一次是在新创建的子进程中。
- 在父进程中, fork 返回子进程的PID。
- 在子进程中, fork 返回0。
因为子进程的PID总是为非零, 返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。
3. 两个例子
两次调用fork。对应的进程图可帮助我们看清这个程序运行了四个进程,每个都调用了一次printf, 这些printf可以以任意顺序执行。
从上两个例子中可以很清楚的看到几个特点:
- fork调用一次,返回两次
第二个例子中
就是指子程序要执行的代码 - 并发执行
父进程和子进程是并发运行的独立进程。内核能够以任意方式交替执行它们的逻辑控制流中的指令。所以第一个例子中的printf的执行顺序是未知的。 - 相同但是独立的地址空间
如果能够在fork函数在父进程和子进程中返回后立即暂停这两个进程,我们会看到两个进程的地址空间都是相同的。每个进程有相同的用户栈、相同的本地变最值、相同的堆、相同的全局变量值,以及相同的代码。在第二个例子中,此时父子空间中x的值都是1。然而,因为父进程和子进程是独立的进程,它们都有自己的私有地址空间。后面,父进程和子进程对x所做的任何改变都是独立的,不会反映在另一个进程的内存中。这就是为什么当父进程和子进程调用它们各自的printf语句时,它们中的变量x会有不同的值。 - 共享文件
父进程和子进程都把它们的输出显示在屏幕上。原因是子进程继承了父进程所有的打开文件。当父进程调用fork 时,
stdout文件是打开的,并指向屏幕。子进程继承了这个文件,因此它的输出也是指向屏幕的。
3、回收子进程
1. 回收流程
已终止的进程不会立即被内核清理,它会保持一种已终止的状态(僵死进程)直到被父进程回收。当父进程回收已终止状态的进程时,内核会将子进程退出时的状态传递给父进程,然后抛弃这个已终止进程。从此这个进程就不复存在了。
如果父进程还没有回收他的僵死子进程就终止了怎么办?
此时会由init进程负责去进行回收。init进程PID =1,是在系统启动时创建,是所有进程的祖先,不会终止。
2.函数调用
默认情况下(options = 0), waitpid 挂起调用进程的执行, 直到它的等待集合(wait set) 中的一个子进程终止。如果等待集合中的一个进程在
刚调用的时刻就已经终止了, 那么waitpid 就立即返回。在这两种情况中, waitpid 返回导致waitpid 返回的已终止子进程的PID。此时, 已终止的子进程已经被回收, 内核会从系统中删除掉它的所有痕迹。
1. 参数pid
2. 参数options
WNOHANG:如果等待集合中任何进程都没有终止,就立即返回。(默认的行为是挂起调用进程,直到有子进程终止)
WUNTRACED:挂起调用进程的执行,直到等待集合中的一个进程变成已终止或者被停止。返回的PID 为导致返回的已终止或被停止子进程的PID。默认的行为是只返回己终止的子进程。
WCONTINUED:挂起调用进程的执行,直到等待集合中一个正在运行的进程终止或等待集合中一个被停止的进程收到SIGCONT 信号重新开始执行。
可以用或运算把这些选项组合起来。例如: WNOHANG I WUNTRACED
立即返回,如果等待集合中的子进程都没有被停止或终止,则返回值为0;如果有一个停止或终止,则返回值为该子进程的PID。
3. 参数statusp
如果statusp参数是非空的,那么waitpid就会在status 中放上关千导致返回的
子进程的状态信息,status 是statusp指向的值。具体内容略。
4错误条件
如果调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD。
如果被一个信号中断,那么它返回- 1,并设置errno为EINTR 。
4、让进程休眠
5、加载并运行程序
execve函数在当前进程的上下文中加载并运行一个新程序。(注意这里没有新创建一个进程,而是在已有的进程中进行加载和运行)
1.参数
filename 代表可执行文件名
argv 代表参数列表,一般argv[0]是可执行文件名字
envp 代表环境变量,每个串都是"name=value"的对
2. 返回值
一般情况下,没有返回值。只有类似filename找不到的情况下才会返回调用程序
3.execve执行
在execve加载了filename 之后,启动代码设置栈, 并将控制传递给新程序的主函数, 该主函数有如下形式的原型
4. 进程与程序
1.二者之间的关系
- 程序是一堆代码和数据,程序可以作为目标文件存在于磁盘上,或者作为段存在于地址空间中
- 进程是执行中程序的一个具体的实例
- 程序总是运行在某个进程的上下文中
2.fork和execve
- fork创建了一个新的子进程,这个子进程是父进程的复制品
- execve是在当前进程的上下文中加载并运行了一个新的程序
6、利用fork和execve运行程序
shell是一个交互型的应用级程序, 它代表用户运行其他程序。最早的shell是sh 程序, 后面出现了一些变种, 比如csh、tcsh、ksh 和bash。shell执行一系列的读/求值(read/evaluate)步骤, 然后终止。读步骤读取来自用户的一个命令行。求值步骤解析命令行, 并代表用户运行程序。
shell首先需要解析以空格分隔的命令行参数, 并构造最终会传递给execve的argv向量。第一个参数被假设为要么是一个内置的shell命令名(ls/pwd等), 马上就会解释这个命令, 要么是一个可执行目标文件, 会在一个新的子进程的上下文中加载并运行这个文件。如果最后一个参数是一个“ & ” 字符,表示应该在后台执行该程序(shell不会等待它完成)。否则, 它返回0, 表示应该在前台执行这个程序,shell会等待它完成。