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;
}

从计算机理论的角度,父子进程具有相同的机会争夺cpu,谁先谁后是随机的; 但是某超级大佬在实验中测试到98%的可能性是父进程获得到CPU,但是无理论依据。
创建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;
}
posted @ 2022-10-27 22:07  mnst  阅读(96)  评论(0)    收藏  举报