Linux 2.进程(ps、top、fork、vfork、wait、waitpid、getpid、getppid)

什么是程序,什么是进程?

程序

就是静态的一个概念。比如 gcc a.c -o a.out
那么这样就会在磁盘生成一个文件 a.out ,这个 a.out 就是一个程序。

进程

进程是我们程序的一次运行活动,当我们去运行这个 a.out 的时候,那么这个程序就跑起来了,此时系统中会多了一个进程。

如何查看系统中有哪些进程?

1、使用 ps 指令查看

ps -aux

实际工作中,配合 grep 来查找程序中是否存在某一进程。
grep 其实是能快速的过滤信息,帮助快速找到想要找的相关信息。

ps -aux|grep init 

这样就能快速找到 init 的相关的进程。

2、使用top指令查看

top

类似Windows下的任务管理器。

什么是进程标识符?

每个进程都有一个非负整数表示的唯一的ID,叫做 pid ,类似身份证。

pid = 0 :成为交换进程(swapper)
作用:进程调度
pid = 1 :init进程
作用:系统初始化

编程调用 getpid 函数获取自身进程的进程标识符。
getppid 获取父进程的进程标识符。

什么是父进程,什么是子进程?

进程A创建了进程B

那么A就是父进程,B就是子进程。父子进程是一个相对的概念,理解为人类中的父子关系。

C程序中的存储空间是如何分配的?

当我们去 ./a.out 运行程序时,操作系统会给程序在内存空间里面,分配出一块地址,这个地址会像上面的图这样。
流程控制,正文,一些算法,if else switch for等等 称为代码段

初始化了的变量:称为数据段

在函数外未初始化的变量:称为bss段

:比如我们调用 malloc 申请空间 我们的返回值会返回在堆

:调用函数的返回值和调用者的环境信息,函数里面定义的变量都会在栈里面。

高地址:就是放 main 函数里面的(int argc,char **argv)
argc argv。

fork函数(创建进程)

fork功能

fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。

其实 fork 之后 会拷贝一份代码给子进程。父进程和子进程都会进行工作。
老Linux是把代码全部拷贝。
新的是采用另一种,一样的则共享,如果子进程修改数据则拷贝一份给子进程,这样父进程的数据和子进程的数据互不影响。

头文件及原型

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

fork参数

void

fork返回值

负值:创建子进程失败。

零:返回到新创建的子进程。

正值:返回父进程或调用者。该值包含新创建的子进程的进程ID。

fork函数使用示例

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
	pid_t pid1;
	pid_t pid2;
	pid_t retpid;
	
	pid1=getpid();
	printf("this is father print pid=%d\n",getpid());
	
	retpid=fork();
	printf("after fork......\n");
	putchar('\n');
	
	pid2=getpid();
	printf("pid2=%d\n",pid2);
	putchar('\n');
	
	if(pid1==pid2)
	{
		printf("this is father pid=%d,retpid=%d\n",getpid(),retpid);
		putchar('\n');
	}
	else
	{
		printf("this is child pid=%d,retpid=%d\n",getpid(),retpid);
		putchar('\n');	
	}
	
	return 0;
}

运行结果:

在这里插入图片描述

fork创建子进程的一般目的是什么?

服务器 和 客户端 的关系。服务器等待客户端的 连接。有一个连接就创建一个子进程去帮助客户端。再来一个客户端连接,再创建一个子进程…

类似场景:你爸爸负责在酒店门口等待客人的到来,来一个你爸爸就创出一个儿子,让这个儿子去招待这位客人,再来一个客人就再创一个儿子,让这个儿子去招待这位客人。
代码实现类似(1)的 操作。

代码实现类似场景。

一个死循环
输入1 则表示有客户端连接进来,通过fork 的返回值 判断是否是当前进程是子进程,如果是,则打印 连接成功 和 ID号。
输入不是1,则打印 没有用户连接。

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main()
{
        pid_t retpid;

        printf("this is father pid=%d\n",getpid());

        int data=0;

        while(1)
        {
                printf("please input a num:\n");
                scanf("%d",&data);

                if(data==1)
                {
                        retpid=fork();

                        if(retpid>0)
                        {

                        }
                        else if(retpid==0)
                        {
                                while(1)
                                {
                                        printf("do request......retpid=%d\n",getpid());
                                        sleep(3);
                                }
                        }
                        else
                        {
                                printf("connect fail!\n");
                                exit(-1);
                        }
                }

                else
                {
                        printf("do nothing......\n");
                }
        }

        return 0;
}

运行结果:

在这里插入图片描述

总结:

在这里插入图片描述

vfork

vfork函数也可以创建进程,vfork 和 fork 有什么区别

  1. 关键区别1.vfork直接用父进程的存储空间,不拷贝。

  2. 关键区别2.vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。(用break退出会影响数据)

vfork测试

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main()
{
        pid_t retpid;
        int cnt=0;

        printf("this is father pid=%d\n",getpid());

        retpid=vfork();

        if(retpid>0)
        {
                while(1)
                {
                        printf("cnt=%d\n",cnt);
                        printf("father's pid=%d\n",getpid());
                        sleep(2);
                }
        }
        else if(retpid==0)
        {
                while(1)
                {
                        printf("child's pid=%d\n",getpid());
                        sleep(2);
                        cnt++;
                        if(cnt==3)
                        {
                                exit(-1);
                        }
                }
        }
        else
        {
                printf("vfork fail\n");
                exit(-1);
        }

        return 0;
}

运行结果:

在这里插入图片描述

在这里定义了一个 data = 0 ;vfork 函数使代码运行起来进入子进程。不断打印ID,当 data = 3 的时候,子进程退出,这时候父进程才开始执行,打印了data 的值,打印父进程的ID。注意一定要用 exit 退出子进程 用 break 会影响data的值。直接破坏掉了。

进程退出

正常退出

  1. main函数调用return
  2. 进程调用exit();标准c库
  3. 进程调用_exit() 或者 _Exit() 属于系统调用。
  4. 进程最后一个线程返回
  5. 最后一个线程调用pthread_exit.

异常退出

  1. 调用abort
  2. 当进程收到某些信号时,比如Ctrl + C
  3. 最后一个线程对取消(cancellation)请求作出响应。

等待子进程退出

为什么要等待子进程退出?

我们创建子进程就是让它干活,那么干活我们会关心结果,有两种,一种是干完活(正常退出 exit)一种是 没干完活 (异常退出 Ctrl + C 或者abort等)。

  1. 等待子进程的退出,并且收集退出的状态。
  2. 子进程退出状态不被收集,会变成僵死进程(僵尸进程)

僵尸进程

僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源。

下面的图片是 如何等待子进程退出( wait 和 waitpid )

在这里插入图片描述

wait、waitpid、waitid

wait功能

父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

wait头文件及原型

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

wait参数

是一个整型数指针

非空:子进程退出状态放在它所指向的地址中
空(NULL):不关心退出状态

wait示例

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main()
{
    pid_t retpid;
    int cnt=0;
    int status=8;

    printf("this is father pid=%d\n",getpid());
    
    retpid=fork();
    
    if(retpid>0)
    {
        while(1)
        {
            wait(&status);
            printf("cnt=%d\n",cnt);
            printf("father's pid=%d,chil's status=%d\n",getpid(),WEXITSTATUS(status));
            sleep(2);
        }
    }
    else if(retpid==0)
    {
        while(1)
        {
            printf("child's pid=%d\n",getpid());
            sleep(3);
            cnt++;
            if(cnt==3)
            {
                printf("EXIT!\n");
                exit(6);
            }
        }
    }
    else
    {
        printf("vfork fail\n");                                                                                                                                                                                                                                   
        exit(-1);
    }
    
    return 0;
}

运行结果:

在这里插入图片描述

总结:

  1. 通过wait函数 因为是exit正常退出 我们得用 WEXITSTATUS 宏来查出exit的值。
  2. 用了wait函数,我们进程不是父进程和子进程一起跑了,而是等待子进程结束,收集子进程的值再执行父进程。如果没有子进程,则立即出错返回。

wait 和 waitpid 的区别

  1. wait 使调用者阻塞(子进程不结束,就一直不会运行父进程)
  2. waitpid 有一个选项可以使调用者不阻塞

waitpid头文件及原型

#include<sys/types.h>
#include<sys/wait.h>

pid_t waitpid(pid_t pid,int *status,int options);
  1. 使用waitpid实现wait的效果
    ret = waitpid(-1, &status, 0); -1表示不等待某个特定PID的子进程而是回收任意一个子进程,0表示用默认的方式(阻塞式)来进行等待,返回值ret是本次回收的子进程的PID

  2. ret = waitpid(pid, &status, 0); 等待回收PID为pid的这个子进程,如果当前进程并没有一个ID号为pid的子进程,则返回值为-1;如果成功回收了pid这个子进程则返回值为回收的进程的PID

  3. ret = waitpid(pid, &status, WNOHANG);这种表示父进程要非阻塞式的回收子进程。此时如果父进程执行waitpid时子进程已经先结束等待回收则waitpid直接回收成功,返回值是回收的子进程的PID;如果父进程waitpid时子进程尚未结束则父进程立刻返回(非阻塞),但是返回值为0(表示回收不成功)。

pid:

在这里插入图片描述

options:

在这里插入图片描述

waitpid应用

ret_wait = waitpid(pid , &status , 0);

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

int main()
{
	int ret_wait = -1;
        int status;

	int pid = fork();

	if(pid > 0)
	{
//		sleep(1);

		printf("This is parent : %d\n",getpid());
//		ret_wait = waitpid(-1 ,&status, 0);
		ret_wait = waitpid(pid , &status , 0);
//		ret_wait = waitpid(pid , &status , WNOHANG);

		printf("Child's process id has been goten : %d\n",ret_wait);

		printf("Child process has normally returned : %d\n",WIFEXITED(status));
		printf("Child process has returned by accident : %d\n",WIFSIGNALED(status));
		printf("Child process has normally returned : %d\n",WEXITSTATUS(status));
	}

	else if(pid == 0)
	{	
//		sleep(1);

		printf("This is child : %d\n",getpid());
		printf("My father's pid : %d\n",getppid());

		return 66;
	}

	return 0;
}

在这里插入图片描述

ret_wait = waitpid(pid , &status , WNOHANG); //非阻塞式

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

int main()
{
	int ret_wait = -1;
        int status;

	int pid = fork();

	if(pid > 0)
	{
//		sleep(1);

		printf("This is parent : %d\n",getpid());
//		ret_wait = waitpid(-1 ,&status, 0);
//		ret_wait = waitpid(pid , &status , 0);
		ret_wait = waitpid(pid , &status , WNOHANG);

		printf("Child's process id has been goten : %d\n",ret_wait);

		printf("Child process has normally returned : %d\n",WIFEXITED(status));
		printf("Child process has returned by accident : %d\n",WIFSIGNALED(status));
		printf("Child process has normally returned : %d\n",WEXITSTATUS(status));
	}

	else if(pid == 0)
	{	
//		sleep(1);

		printf("This is child : %d\n",getpid());
		printf("My father's pid : %d\n",getppid());

		return 66;
	}

	return 0;
}

在这里插入图片描述

孤儿进程

父进程如果不等待子进程的退出,在子进程之前就“结束”了自己的生命,此时子进程叫孤儿进程。
Linux 避免系统存在过多的孤儿进程,init 进程收留孤儿进程,变成孤儿进程的父进程。

特殊情况:在Ubuntu中孤儿进程是被 systemd 的 user 收留的,而不是被init,所以有时候 getppid() 可能不为1

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main()
{
	pid_t retpid;
	int cnt=0;
    int status=8;

	retpid=fork();
	
	if(retpid>0)
	{
		printf("father's pid=%d\n",getpid());
	}

	else if(retpid==0)
	{
		while(1)
		{
			printf("child's pid=%d\tfather's pid=%d\n",getpid(),getppid());
            sleep(1);
			cnt++;
			if(cnt==3)
			{
                printf("EXIT!\n");
                exit(6);
			}
		}
	}
	else
	{
		printf("fork fail\n");
		exit(-1);
	}

	return 0;
}

在这里插入图片描述

在这里插入图片描述

getppid

getppid头文件及原型

#include<unistd.h>

pid_t getppid(void);

getppid函数说明

getppid() 用来取得目前进程的父进程识别码。

getppid返回值

目前进程的父进程识别码。

  • 6
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值