进程控制

fork函数

创建一个子进程。

pid_t fork(void); 失败返回-1;成功返回:① 父进程返回子进程的ID(非负) ②子进程返回 0

pid_t类型表示进程ID,但为了表示-1,它是有符号整型。(0不是有效进程ID,init最小,为1)

注意返回值,不是fork函数能返回两个值,而是fork后,fork函数变为两个,父子需【各自】返回一个。

循环创建n个子进程

一次fork函数调用可以创建一个子进程。那么创建N个子进程应该怎样实现呢?

简单想,for(i = 0; i < n; i++) { fork() } 即可。但这样创建的是N个子进程吗?

循环创建N个子进程

从上图我们可以很清晰的看到,当n为3时候,循环创建了(2^n)-1个子进程,而不是N的子进程。需要在循环的过程,保证子进程不再执行fork ,因此当(fork() == 0)时,子进程应该立即break;才正确。

练习:通过命令行参数指定创建进程的个数,每个进程休眠1S打印自己是第几个被创建的进程。如:第1个子进程休眠0秒打印:“我是第1个子进程”;第2个进程休眠1秒打印:“我是第2个子进程”;第3个进程休眠2秒打印:“我是第3个子进程”。 【fork1.c】

通过该练习掌握框架:循环创建n个子进程,使用循环因子i对创建的子进程加以区分。

getpid函数

获取当前进程ID

pid_t getpid(void);

getppid函数

获取当前进程的父进程ID

pid_t getppid(void);

区分一个函数是“系统函数”还是“库函数”依据:

②  是否访问内核数据结构

② 是否访问外部硬件资源 二者有任一 → 系统函数;二者均无 → 库函数

getuid函数

获取当前进程实际用户ID

uid_t getuid(void);

获取当前进程有效用户ID

uid_t geteuid(void);

getgid函数

获取当前进程使用用户组ID

gid_t getgid(void);

获取当前进程有效用户组ID

gid_t getegid(void);

进程共享

父子进程之间在fork后。有哪些相同,那些相异之处呢?

刚fork之后:

父子相同处: 全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式...

父子不同处: 1.进程ID   2.fork返回值   3.父进程ID    4.进程运行时间    5.闹钟(定时器)   6.未决信号集

似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但pid不同。真的每fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后在映射至物理内存吗?

当然不是!父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。

练习:编写程序测试,父子进程是否共享全局变。     【fork_shared.c】

重点注意!躲避父子进程共享全局变量的知识误区!

【重点】:父子进程共享:1. 文件描述符(打开文件的结构体)  2. mmap建立的映射区 (进程间通信详解)

特别的,fork之后父进程先执行还是子进程先执行不确定。取决于内核所使用的调度算法。

gdb调试

使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。

set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程。

set follow-fork-mode parent 设置跟踪父进程。

注意,一定要在fork函数调用之前设置才有效。 【follow_fork.c】

exec函数族

fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。

其实有六种以exec开头的函数,统称exec函数:

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 *path, char *const argv[], char *const envp[]);

execlp函数

加载一个进程,借助PATH环境变量

int execlp(const char *file, const char *arg, ...); 成功:无返回;失败:-1

参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。

该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。

execl函数

加载一个进程, 通过 路径+程序名 来加载。

int execl(const char *path, const char *arg, ...); 成功:无返回;失败:-1

对比execlp,如加载"ls"命令带有-l,-F参数

execlp("ls", "ls", "-l", "-F", NULL);      使用程序名在PATH中搜索。

execl("/bin/ls", "ls", "-l", "-F", NULL);    使用参数1给出的绝对路径搜索。

execvp函数

加载一个进程,使用自定义环境变量env

int execvp(const char *file, const char *argv[]);

变参形式: ①... ② argv[]  (main函数也是变参函数,形式上等同于 int main(int argc, char *argv0, ...))

变参终止条件:① NULL结尾 ② 固参指定

execvp与execlp参数形式不同,原理一致。

练习:将当前系统中的进程信息,打印到文件中。 【exec_ps.c】

exec函数族一般规律

exec函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值-1。所以通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。

l (list) 命令行参数列表

p (path) 搜素file时使用path变量

v (vector) 使用命令行参数数组

e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量

事实上,只有execve是真正的系统调用,其它五个函数最终都调用execve,所以execve在man手册第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。

exec函数族

回收子进程

孤儿进程

孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。

【orphan.c】

僵尸进程

僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。

【zoom .c】

特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。思考!用什么办法可清除掉僵尸进程呢?

wait函数

一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。

父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:

① 阻塞等待子进程退出

② 回收子进程残留资源

③ 获取子进程结束状态(退出原因)。

 pid_t wait(int *status); 成功:清理掉的子进程ID;失败:-1 (没有子进程)

当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)

可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:

1.  WIFEXITED(status) 为非0 → 进程正常结束

WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)

2. WIFSIGNALED(status) 为非0 → 进程异常终止

WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。

*3. WIFSTOPPED(status) 为非0 → 进程处于暂停状态

WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。

WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行

【wait1.c、wait2.c】

waitpid函数

作用同wait,但可指定pid进程清理,可以不阻塞。

    pid_t waitpid(pid_t pid, int *status, in options);成功:返回清理掉的子进程ID;失败:-1(无子进程)

特殊参数和返回情况:

参数pid:

> 0 回收指定ID的子进程

-1 回收任意子进程(相当于wait)

0 回收和当前调用waitpid一个组的所有子进程

< -1 回收指定进程组内的任意子进程

返回0:参3为WNOHANG,且子进程正在运行。

注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。 【waitpid.c】

作业:父进程fork 3 个子进程,三个子进程一个调用ps命令, 一个调用自定义程序1(正常),一个调用自定义程序2(会出段错误)。父进程使用waitpid对其子进程进行回收。

linux进程控制函数详解的更多相关文章

  1. linux进程地址空间详解(转载)

    linux进程地址空间详解(转载) 在前面的<对一个程序在内存中的分析 >中很好的描述了程序在内存中的布局,这里对这个结果做些总结和实验验证.下面以Linux为例(实验结果显示window ...

  2. Linux进程退出详解(do_exit)--Linux进程的管理与调度(十四)

    Linux进程的退出 linux下进程退出的方式 正常退出 从main函数返回return 调用exit 调用_exit 异常退出 调用abort 由信号终止 _exit, exit和_Exit的区别 ...

  3. Linux进程数据结构详解

    1.Linux的进程简介: 支持多线程的操作系统中,进程是资源分配的最小单位,线程是调度的基本单位.Linux是现代的32位或64位的支持多线程的操作系统,不过Linux是一种以轻量级进程作为线程,多 ...

  4. Linux进程管理详解

    何谓进程?进程,就是正在执行的一个程序或命令,每一个进程都是一个运行实体,有自己的地址空间,并占用一定的系统资源.简而言之,进程就是运行中的程序.在Linux中,诸如ls等命令都是进程,只不过某些命令 ...

  5. Linux串口编程详解(转)

    串口本身,标准和硬件 † 串口是计算机上的串行通讯的物理接口.计算机历史上,串口曾经被广泛用于连接计算机和终端设备和各种外部设备.虽然以太网接口和USB接口也是以一个串行流进行数据传送的,但是串口连接 ...

  6. Linux启动过程详解(inittab、rc.sysinit、rcX.d、rc.local)

    启动第一步--加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备启动顺序信息.硬 ...

  7. Linux启动过程详解

    Linux启动过程详解 附上两张图,加深记忆 图1: 图2: 第一张图比较简洁明了,下面对第一张图的步骤进行详解: 加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的 ...

  8. Linux /dev目录详解和Linux系统各个目录的作用

    Linux /dev目录详解(转http://blog.csdn.net/maopig/article/details/7195048) 在linux下,/dev目录是很重要的,各种设备都在下面.下面 ...

  9. Linux netstat命令详解

    Linux netstat命令详解 一  简介 Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多 ...

随机推荐

  1. shp系列(一)——利用C++进行shp文件的读(打开)与写(创建)开言

    博客背景和目的 最近在用C++写一个底层的东西,需要读取和创建shp文件.虽然接触shp文件已经几年了,但是对于shp文件内到底包含什么东西一直是一知半解.以前使用shp文件都是利用软件(如ArcGI ...

  2. ubuntu下谷歌浏览器字体模糊解决方案

    一般来说,这种问题应该是缺少字体造成的,因此重新安装字体就可以解决问题. 1.双系统下,查找Windows C盘下:Windows/font/???? ???可在3中查找相应字体 2.分别双击安装那两 ...

  3. MVC的一些常用特性,持续更新中。。。

    1. @MvcHtmlString.Create("<option value='1'>火星</option>")   //渲染Html

  4. JavaScript中闭包的理解

    1.什么是闭包 我个人理解闭包就是函数中嵌套函数,但是嵌套的那个函数必须是返回值,才构成闭包: <!DOCTYPE html> <html> <head> < ...

  5. Axure设计软件下载安装及注册

    如图:本地计算机安装了两个版本的Axure,但8版的有问题,点击保存软件就退出了:7版本要想使用的注册授权 https://blog.csdn.net/botree_chan/article/deta ...

  6. 常用几个空格的 Unicode 码

    const SPACE_UNICODE = { 'ensp': '\u2002', 'emsp': '\u2003', 'nbsp': '\u00a0' }

  7. (转)JobTracker和TaskTracker概述

    一 概述: (1)Hadoop MapReduce采用Master/Slave结构. *Master:是整个集群的唯一的全局管理者,功能包括:作业管理.状态监控和任务调度等,即MapReduce中的J ...

  8. Ibatis在运行期得到可执行到sql

    环境:oracle-11g ,ibatis-2.0 ,java-1.7 最近因为有个需要是在程序中得到ibatis到sql字符串,即通过以下的ibatis配置得到sql语句 <select id ...

  9. [luogu4133 BJOI2012] 最多的方案 (计数dp)

    题目描述 第二关和很出名的斐波那契数列有关,地球上的OIer都知道:F1=1, F2=2, Fi = Fi-1 + Fi-2,每一项都可以称为斐波那契数.现在给一个正整数N,它可以写成一些斐波那契数的 ...

  10. 紫书 例题8-2 UVa 11605(构造法)

    这道题方法非常的巧妙, 两层的n*n, 第一层第I行全是第I个国家, 第二层的第j列全是第j个国家.这样能符合题目的条件.比如说第1个国家, 在第一层的第一行全是A, 然后在第二层的第一行就有ABCD ...