接下来一节就是进程管理
在这一节里边我们涉及到几个主题
一个是进程的创建
第二个是进程的调度
接下来是进程的终止
然后涉及到僵尸进程
子进程的异步清除和守护进程
该怎么去处理
我们首先来看进程的创建
我们使用system()这个函数
在程序中执行一条命令
这是一个非常简单的函数
system()后面跟着一个命令的一个字符串
它就会执行这个命令
在Bourne shell里边
系统会创建一个子进程
来执行我们这个命令
返回值就是那个shell的退出状态
我们程序做完了
它退出状态是什么
这个函数
system()这个函数就会返回这个东西
如果这个shell不能运行
返回值就是127
如果发生其它错误
返回值就是-1了
就这个意思 很典型
你看我们system()执行一个命令
“ls -l /”
把根目录 列一下目录
以一个列表的形式详细地列出来
它就会把这个结果返回给ret_val
这个结果是这个shell做完了以后的结果
不是列的实际目录呈现的那个数据
不是那个
这是system()这个函数
总体上使用起来是非常简单的
在创建进程的时候
最重要的函数是下面这个 fork()
创建当前进程一个副本
作为一个子进程
这个函数调用其实非常简单 没有参数
返回值其实是一个整数
你看返回一个整数
错 fork()这个函数特别特殊
因为它一次调用返回两个整数
怎么回事
因为它要创建的是一个子进程
它把当前这个进程拷贝一份
形成一个子进程
它像什么呢 它像细胞的分裂
一个细胞然后一分裂 一分为二
两个细胞一模一样
一个是原先的进程 一个是新的进程
fork()干的就是这个事
你在一个进程上面调用一个fork()
就会创建一个子进程
这个子进程所使用到的所有的东西
都和父进程是一模一样的
它是一个副本
所以这个fork()这个函数
将会返回两个值
我们的程序代码中
就应该通过这个返回值
来确定它是在子进程里
还是在父进程里
当它的返回值是0的时候
说明它是子进程
当它返回值是非零的时候
尤其是大于0值的时候
说明它是父进程
为啥呀 它返回是子进程的ID
子进程又没有调用fork()创建孙子进程
对不对 它哪有子进程ID
子进程ID只有父进程才具有
所以在子进程内部
它返回的那个值是0
在父进程的时候
看到的那个返回值
是子进程创建出来那个PID
看例子你就明白了
我们使用fork()创建进程的一个副本
函数调用特简单
定义一个pid_t类型的变量
child_pid
把它初始化为fork()系统调用
从此一个新的进程就产生出来了
if( child_pid != 0 )
说明我们是在父进程里
我们就输出这个信息
否则说明它在子进程里
child_pid是0
我们在子进程里
我们就输出子进程这个信息
这个程序的实现
当然是有一点问题的
我们后面会讨论
因为它没有考虑到错误处理的问题
我们后面会讨论这一点
但是在此同学们要特别记住
fork()这个函数返回两个值
一个值是给父进程用的
一个值是给子进程用的
我们编写这个程序的时候
就可以通过一个if-else
来将父进程的代码
和子进程的代码分开
让这一段代码在父进程里用
让这段代码在子进程里用
你看 让这段代码在父进程里执行
让这段代码在子进程里执行
就是通过这个fork()
这个函数的返回值来做到这一点
两个进程是一模一样的
就是原来进程一个副本嘛
此前用到的环境变量
什么参数 什么打开文件描述符
都是一模一样的
它就制造一个副本
但是在fork()之后
我们就可以让这两个进程
执行不同的代码了
这是一个非常精巧地
而且资源消耗量非常少地
创建子进程的模式
是早期UNIX操作系统给出的实现策略
现在回过头来看 它虽然轻巧
但是很多时候
它给我们创建进程的模式
其实不够灵活
你只能创建当前进程的副本
我们创建一个子进程 干嘛呢
早期最重要的工作
就是在创建的子进程里边
执行特定的命令
我们可以使用exec()这个函数
来执行这个命令
exec()这个函数不是一个 很多个
execl()、execlp()、execle()、execv()、
execvp()、execvpe()
好多个函数 你都可以用
它们有不同的意义
参数的列表其实也不一样
因为早期没有函数重载
都是按照这个模式写
如果有函数重载
很多函数名字都可以不用
如果这个函数名字里边有字母“p”
就表示它接受程序名作为参数
不包括“p”
那么你就必须提供这个程序的
完整路径
如果它有一个字母“v”
它就表示接受以NULL字符
结尾的参数列表
如果有一个字母“l”
它就接受C格式的
可变参数的那个列表
如果有字母“e”的
就表示接受一个附加的环境参数
就这个模式 需要的时候
就选它对应的exec()的命令就可以了
你比如讲我们看这个例子
它执行命令的基本模式就这个样子
在程序中我们调用一个fork()
创建一个子进程
然后在这个子进程里面
执行一个特定的命令
我们就调用exec()这个函数调用
我们实现一个函数叫spawn()
在这里执行一个特定的命令
参数是在这里 程序在这里面
传程序的名字 传参数列表
参数列表是这样
char * args[] = { "ls", "-l", "/", NULL };
它是一个参数、一个参数、一个参数传的
它是一个字符串数组
最后当然会有一个NULL 别忘了
我们会调用spawn()这个函数
来执行这个命令
我们看spawn()这个函数是怎么实现的
spawn()这个函数首先会fork()
我们会创建一个子进程
当这个子进程的PID不是0的时候
说明它是一个父进程
我们直接就return它的子进程ID 就OK了
否则说明它是在子进程里
那么我们就execvp()
就在子进程里执行我们这个程序
然后我们输出错误消息
注意 这个子进程execvp()这个函数
只有在发生错误的时候 它才会返回
如果不发生错误 它就不返回了
启动一个新的子进程
然后就在那个子进程里面
执行你那个程序嘛
执行完了 它不就做完了嘛
它不会返回的
有错了 它才会返回给你
std：cerr
这个串才能向标准错误流里面输出
这一行才有意义
平时它是不会运行的
如果正确运行的话下面是不会去做的
特别注意这一点