#include
pid_t fork(void);
返回值:自进程返回0,父进程返回子进程id,错误返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核作用:
1)分配新的内存块和pcb给子进程
2)将父进程部分pcb内容拷贝到子进程
3)添加子进程到系统进程列表中
4)fork函数返回,开始调度器调度
fork函数调用失败的原因:①系统中有太多的进程,系统资源不足以创建新的子进程②实际用户的进程数量超过了限制
一般情况下,父子代码共享,父子在不写入的时候数据也是共享的。当任意一方需要进行写入,则以写时拷贝的方式对需要的部分进行拷贝。
为什么采用写时拷贝,而不是直接复制一份代码运行?
写时拷贝本质上是一种按需申请资源的策略,当一个子进程创建,如果子进程并不需要新的物理内存空间,则与父进程共用同一块空间。减少资源的浪费,操作系统不允许资源的低利用率。
简单来讲,程序运行就是进程。进程终止有两种情况①程序正常执行完毕(执行结果正确/执行结果错误)②程序运行崩溃,也就是进程异常
在 Unix 和 Linux 系统中,程序可以在执行终止后传递值给其父进程,这个值被称为退出码(exit code)或退出状态(exit status)。
一个成功结束的命令的退出状态码是0,如果一个命令结束时有错误,退出状态码就是一个正数值(1-255)。
int main()
{
return 0;
}
这里的 0 就是进程的退出码。正常执行完毕,结果正确 返回0;结果错误 返回其他值,不同的数值标识不同的错误原因,用于用户进行进程退出健康状况的判断
#include
int add_totop(int top)
{
int sum=0;
for(int i=0;i
通过进程的退出码可以判断程序是否正常执行,echo $?打印进程的退出码并且只显示最近一次进程的退出码
进程终止也就是操作系统内部减少一个进程,操作系统在进程结束后会将进程对应的pcb、代码和独立数据释放
Linux上执行exit可使shell以指定的状态值退出。若不设置状态值参数,则shell以预设值退出。状态值0代表执行成功,其他值代表执行失败。
在代码的任意地方调用exit函数,都表示进程的退出
exit函数是一个C语言库函数,而_exit函数是一个系统函数调用。其功能和作用并没有差别
exit()与_exit()函数最大的区别在于:
exit()函数在调用exit系统之前要检查文件的打开情况,把文件缓冲区的内容写回文件。由于Linux标准函数中,“缓冲I/O”的操作,其特征即对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续读出若干条记录,在下次读文件时就可以直接从内存的缓冲区读取;同样每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度。
_exit()函数直接终止函数
exit函数调用相当于:执行清理函数 -> 关闭所有打开的流,写入所有缓冲数据 -> _exit
运行状态是进程获得cpu使用权,正在执行代码的状态; 等待状态是阻塞状态
进程等待:就是通过系统调用,获取子进程退出码或者退出信号的方式,同时释放内存
进程等待的必要性:
①子进程退出,父进程没有读取子进程的退出码,造成僵尸进程问题,导致内存泄漏
②进程如果变成僵尸进程,则属于一种“假死”状态,kill -9也无法将其释放,因为该进程以及终止
③子进程承担部分父进程功能,父进程需要知道子进程处理结果,也就是父进程需要子进程的退出码信息
④父进程通过进程等待,回收子进程资源,获取子进程退出信息
进程运行完毕的结果通过退出码展现,代码运行异常通过信号展现。因此采用信号+退出码的方案
#include
#include
int wait(int *status)
//参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。
//但如果不关心子进程如何死掉,只想消灭僵尸进程,我们就可以设定这个参数为NULL
wait函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID
#include
#include
pid_t waitpid(pid_t pid,int *status,int options);
1)pid_t pid:
pid>0 | 等待进程号为pid的子进程。 |
pid=-1 | 等待任何子进程,此时的waitpid()函数就退化成了普通的wait()函数。 |
2)int *status
这个参数将保存子进程的状态信息,有了这个信息父进程就可以了解子进程为什么会推出,是正常退出还是出现错误。如果status不是空指针,则写入状态信息.
这里并不能将status视为一个简单的整数,而是看作一个位图,status长度为32个比特位,对于高位的16个比特位(16-31)不做研究,第8-15位表示进程的退出状态(也就是exit(status),退出码),第0-7位为进程终止信号。
15-8比特位 | 7-0比特位 | |
正常退出 | 退出状态 | 0(退出码) |
程序错误 | 未使用 | 终止信号 |
宏 | 说明 |
WIFEXITED(status) | 如果子进程正常结束,它就返回真;否则返回假 |
WEXITSTATUS(status) | 如果WIFEXITED(status)为真,则可以用该宏取得子进程exit()返回的结束代码 |
WIFSIGNALED(status) | 如果子进程因为一个未捕获的信号而终止,它就返回真;否则返回假 |
WTERMSIG(status) | 如果WIFSIGNALED(status)为真,则可以用该宏获得导致子进程终止的信号代码 |
WIFSTOPPED(status) | 如果当前子进程被暂停了,则返回真;否则返回假 |
WSTOPSIG(status) | 如果WIFSTOPPED(status)为真,则可以使用该宏获得导致子进程暂停的信号代码 |
3)int options
参数options提供了一些另外的选项来控制waitpid()函数的行为。如果不想使用这些选项,则可以把这个参数设为0
参数 | 说明 |
WNOHANG | 如果pid指定的子进程没有结束,则waitpid()函数立即返回0,而不是阻塞在这个函数上等待;如果结束了,则返回该子进程的进程号。 |
WUNTRACED | 如果子进程进入暂停状态,则马上返回。 |
在子进程没有退出的时候,父进程只能一直调用waitpid进行等待,这种状况叫做阻塞等待。这种等待并不是运行状态,也不在运行队列中,而是在阻塞队列中
阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
socket 接收数据函数 recv 是一个阻塞调用的例子。当 socket 工作在阻塞模式的时候, 如果没有数据的情况下调用该函数,则当前线程就会被挂起,直到有数据为止。
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。