linux进程大汇总
linux进程大汇总
fork函数
返回值有2个: 本质上是一个进程--> 2个进程 ---> 各自对fork做返回.
1. 父进程:返回子进程的pid (非负整数 > 0)
2. 子进程:返回0,而且说明进程创建成功
最简单的创建进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
printf("xxxxxxxxxxx\n");
pid = fork();
if (pid == -1 ) {
perror("fork error:");
// exit(1);
} else if (pid > 0) {
printf("I'm parent pid = %d, parentID = %d\n", getpid(), getppid());
} else if (pid == 0) {
printf("I'm child pid = %d, parentID=%d\n", getpid(), getppid());
// sleep(1);
}
printf("yyyyyyyyyyyyyy\n");
return 0;
}
创建5个子进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
int i;
pid_t pid;
printf("xxxxxxxxxxx\n");
for (i = 0; i < 5; i++) {
pid = fork();
if (pid == 0)
break;
}
if (i < 5) {
sleep(i);
printf("I'am %d child , pid = %u\n", i+1, getpid());
} else {
sleep(i);
printf("I'm parent\n");
}
return 0;
}
进程共享
父子进程之间在fork后。有哪些相同,那些相异之处呢?
刚fork之后:父子相同处: 全局变量、.data、.text、栈、堆、
环境变量、用户ID、宿主目录、进程工作目录、信号处理方式...
父子不同处: 1.进程ID 2.fork返回值 3.父进程ID
4.进程运行时间 5.闹钟(定时器) 6.未决信号集
似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,
但pid不同。真的每fork一个子进程都要将父进程的0-3G地址空间
完全拷贝一份,然后在映射至物理内存吗?
答:当然不是!父子进程间遵循读时共享写时复制的原则。
这样设计,无论子进程执行父进程的逻辑还是执行自己的
逻辑都能节省内存开销。
练习:编写程序测试,父子进程是否共享全局变量。
父子进程对全局变量/公共资源的影响
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int var = 34;
int main(void)
{
pid_t pid;
printf("xxxxxxxxxxx\n");
pid = fork();
if (pid == -1 ) {
perror("fork error:");
exit(1);
} else if (pid > 0) {
//sleep(2);
var = 55;
printf("I'm parent pid = %d, parentID = %d, var = %d\n", getpid(), getppid(), var);
} else if (pid == 0) {
//sleep(2);
var = 100;
printf("child pid = %d, parentID=%d, var = %d\n", getpid(), getppid(), var);
}
printf("var = %d\n", var);
return 0;
}
gdb调试
使用gdb调试的时候,gdb只能跟踪一个进程。
可以在fork函数调用之前,通过指令
设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。
set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程。
set follow-fork-mode parent 设置跟踪父进程。
注意,一定要在fork函数调用之前设置才有效。
僵尸进程与孤儿进程
僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。思考!用什么办法可清除掉僵尸进程呢?
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
孤儿进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid == 0) {
while (1) {
printf("I am child, my parent pid = %d\n", getppid());
sleep(1);
}
} else if (pid > 0) {
printf("I am parent, my pid is = %d\n", getpid());
sleep(9);
printf("------------parent going to die------------\n");
} else {
perror("fork");
return 1;
}
return 0;
}
僵尸进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid, wpid;
pid = fork();
if (pid == 0) {
printf("---child, my parent= %d, going to sleep 10s\n", getppid());
sleep(6);
printf("-------------child die--------------\n");
} else if (pid > 0) {
while (1) {
printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);
sleep(1);
}
} else {
perror("fork");
return 1;
}
return 0;
}
wait和waitpid回收子进程
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】
wait回收子进程
最简单的阻塞等待回收子进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid, wpid;
pid = fork();
if (pid == 0) {
// 自己在abnor程序中设置相关错误
execl("abnor","abnor",NULL);
printf("---child, my parent= %d, going to sleep 6s\n", getppid());
sleep(6);
printf("-------------child die--------------\n");
} else if (pid > 0) {
//阻塞等待回收子进程,并回收了子进程的残留资源
wpid = wait(NULL);
if(wpid == -1){
perror("fork");
exit(1);
}
while (1) {
printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);
sleep(1);
}
} else {
perror("fork");
return 1;
}
return 0;
}
获取子进程死亡的状态
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid, wpid;
int status;
pid = fork();
if (pid == 0) {
printf("---child, my parent= %d, going to sleep 6s\n", getppid());
sleep(100);
printf("-------------child die--------------\n");
// exit(76);
return 109;
} else if (pid > 0) {
//阻塞等待回收子进程,并回收了子进程的残留资源
wpid = wait(&status);
if(wpid == -1){
perror("fork");
exit(1);
}
if (WIFEXITED(status)) { //为真说明子进程正常结束
printf("child exit with %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) { //为真说明子进程被信号终止(异常)
printf("child is killed by %d\n", WTERMSIG(status));
}
while (1) {
printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);
sleep(1);
}
} else {
perror("fork");
return 1;
}
return 0;
}
waitpid回收子进程
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调用只能清理一个子进程,清理多个子进程应使用循环。
回收子进程的不同方式
回收子进程的不同方式1
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int n = 5, i; //默认创建5个子进程
pid_t p, q;
pid_t wpid;
if(argc == 2){
n = atoi(argv[1]);
}
for(i = 0; i < n; i++) {//出口1,父进程专用出口
p = fork();
if(p == 0) {
break; //出口2,子进程出口,i不自增
}else if(i == 3){
q = p; // 保存某一个确定的子进程的id号,用于指定回收子进程
}
}
if(n == i){
sleep(n);
printf("I am parent, pid = %d\n", getgid());
//测试时选择以下情况其中之一,注意不同情况的对比
//情况1:回收某1个子进程,不知道回收的谁
//wait(NULL)
//情况2:通过wait回收所有的子进程
//while(wait(NULL));
//情况3:回收一个指定的子进程,通过q保存的进程id号
//waitpid(q,NULL,0);
//情况4:通过waitpid回收所有的子进程
while(waitpid(-1,NULL,0)); // 相当于while(wait(NULL));
while(1);
} else {
sleep(i);
printf("I'm %dth child, pid = %d, gpid=%d\n",
i+1, getpid(), getgid());
}
return 0;
}
回收子进程的不同方式2-之剖析waitpid和wait的不同之处
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int n = 5, i; //默认创建5个子进程
pid_t p, q;
pid_t wpid;
if(argc == 2){
n = atoi(argv[1]);
}
for(i = 0; i < n; i++) {//出口1,父进程专用出口
p = fork();
if(p == 0) {
break; //出口2,子进程出口,i不自增
}else if(i == 3){
q = p; // 保存某一个确定的子进程的id号,用于指定回收子进程
}
}
if(n == i){
sleep(n);
printf("I am parent, pid = %d\n", getgid());
//测试时选择以下情况其中之一,注意不同情况的对比
//情况5:重点理解
//情况5:通过waitpid的第3个参数,剖析waitpid和wait的不同之处
do{
wpid = waitpid(-1,NULL,WNOHANG);
// 注意返回值
// 第3个参数为WNOHANG的前提下:if wpid ==0 说明子进程正在运行
if(wpid>0){
n--;
}
sleep(1);
}while(n >= 0);
printf("waitpid finish\n");
} else {
sleep(i);
printf("I'm %dth child, pid = %d, gpid=%d\n",
i+1, getpid(), getgid());
}
return 0;
}
# 作业:父进程fork 3 个子进程,三个子进程一个调用ps命令, 一个调用自定义程序1(正常),一个调用自定义程序2(会出段错误)。父进程使用waitpid对其子进程进行回收。
点击查看代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(){
const int n=3;
int i=0,status;
pid_t p,wpid;
for(i = 0; i < n; i++){
p= fork();
if(p == 0)
break;
// q[i] = p;
}
if(i==0){ //子进程1:执行ps指令
sleep(i+1);
printf("I'm %dth child, pid = %d, gppid=%d\n",
i+1, getpid(), getgid());
execlp("ps","ps",NULL);
}
else if(i==1){ //子进程2:调用正常的自定义程序
sleep(i+1);
printf("I'm %dth child, pid = %d, gppid=%d\n",
i+1, getpid(), getgid());
execl("normal","normal",NULL);
}
else if(i==2){ //子进程3:调用正常的自定义程序
sleep(i+1);
printf("I'm %dth child, pid = %d, gppid=%d\n",
i+1, getpid(), getgid());
execl("abnor","abnor",NULL);
}
else{
int N=n;
sleep(n);
do{
wpid = waitpid(-1,&status,WNOHANG);
if(wpid == -1){
perror("fork");
exit(1);
}
if (WIFEXITED(status)) { //为真说明子进程正常结束
printf("child exit with %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) { //为真说明子进程被信号终止(异常)
printf("child is killed by %d\n", WTERMSIG(status));
}
if(wpid>0){
N--;
}
sleep(1);
}while(N > 0);
printf("waitpid finish\n");
}
return 0;
}
浙公网安备 33010602011771号