Linux进程

目录

0.基本概述

  • 程序概述
    • 程序就是静态的一个概念。简单的说就是比如 gcc a.c -o a 那么这样就会在磁盘生成一个文件a,这个a 就是一个程序。
    • 程序通常以两种面目示人。其一为源码形式,由使用编程语言(比如,C 语言)写成的一系列语句组成,是人类可以阅读的文本文件。要想执行程序,则需将源码转换为第二种形式—计算机可以理解的二进制机器语言指令。(这与脚本形成了鲜明对照,脚本是包含命令的文本文件,可以由 shell 或其他命令解释器之类的程序直接处理。)一般认为,术语“程序”的上述两种含义几近相同,因为经过编译和链接处理,会将源码转换为语义相同的二进制机器码。
  • 进程概述
    • 进程是我们程序的一次运行活动,当我们去运行这个a 的时候,那么这个程序就跑起来了,此时系统中会多了一个进程。

    • 简而言之,进程是正在执行的程序实例。 执行程序时,内核会将程序代码载入虚拟内存,
      为程序变量分配空间,建立内核记账(bookkeeping)数据结构,以记录与进程有关的各种信
      息(比如,进程 ID、用户 ID、组 ID 以及终止状态等)。

    • 在内核看来,进程是一个个实体,内核必须在它们之间共享各种计算机资源。对于像内
      存这样的受限资源来说,内核一开始会为进程分配一定数量的资源,并在进程的生命周期内,
      统筹该进程和整个系统对资源的需求,对这一分配进行调整。程序终止时,内核会释放所有
      此类资源,供其他进程重新使用。其他资源(如 CPU、网络带宽等)都属于可再生资源,但
      必须在所有进程间平等共享。

  • 进程内存布局
    • 逻辑上将一个进程划分为以下几部分(也称为段)。
    • 文本: 程序的指令
    • 数据: 程序使用的静态变量
    • 堆: 程序可从该区域动态分配额外内存
    • 栈: 随函数调用、返回而增减的一片内存,用于为局部变量和函数调用链接信息分配
      存储空间

1.如何创建进程

  • 进程可使用系统调用 fork() 来创建一个新进程。调用 fork() 的进程被称为父进程新创建的进程则被称为子进程。内核通过对父进程的复制来创建子进程。子进程从父进程处继承数据段、栈段以及堆段的副本后,可以修改这些内容,不会影响父进程的“原版”内容。(在内存中被标记为只读的程序文本段则由父、子进程共享。)

头文件

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

函数原型

pid_t fork(void);

返回值

  • 注意,这个跟我们正常的函数不一样,正常的函数返回值都是只有一个,但是fork() 返回值是有两个的,因为我们还创建了一个新的进程,所以是2个进程。

  • 失败: 返回 -1

  • 成功: 如果等于0 则是子进程 , 大于0 则是父进程.

代码示例

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


int main()
{
	pid_t pid;

	pid = fork();
	if(-1 == pid)
	{
		perror("fork");
		return -1;
	}	
	/* 子进程代码 */
	else if(0 == pid)
	{
		printf("我是子进程  我的进程号:%d  我的父进程号:%d\n",getpid(),getppid());
		exit(1);
	}
	/* 父进程代码 */
	else
	{
		printf("我是父进程  我的进程号:%d  pid 的值  = :%d\n",getpid(),pid);

		/* 等待子进程结束 回收它再退出 */	
		int status;	
		wait(&status);

		/* 如果是正常结束 */
		if(WIFEXITED(status))
		{
			printf("子进程正常退出。状态号:%d\n",WEXITSTATUS(status));
		}
	}
	return 0;
}

运行结果如下
在这里插入图片描述

上述还用到了几个函数,下面会补充。

总结:

  1. fork() 有两个返回值 0是子进程 大于0是父进程
  2. 父进程返回的pid值 是 子进程的 ID号。

2.如何查看进程

  • 命令: ps -auxps -elf
    • 实际工作中,配合 grep 来查找程序中是否存在某一进程. ps -elf | grep init 这样就能快速找到 init 的 相关的进程。
  • 命令: top
    • 类似Windows下的任务管理器

3.进程号如何获取

  • 进程号和父进程号
    • 每个进程都有一个进程号(PID),进程号是一个正数,用以唯一标识系统中的某个进程。对各种系统调用而言,进程号有时可以作为传入参数,有时可以作为返回值。比如,系统调用 kill() 去杀死某个进程、允许调用者向拥有特定进程号的进程发送一个信号。当需要创建一个对某进程而言唯一的标识符时,进程号就会派上用场。常见的例子是将进程号作为与进程相关文件名的一部分。系统调用 getpid()返回调用进程的进程号。
    • 每个进程都有一个创建自己的父进程。使用系统调用 getppid()可以检索到父进程的进程号。

为了程序没那么快退出,我这里整个死循环,这样可以通过命令 ps -elf 查看进程号,来查看我们用的 getpid() getppid()函数

头文件

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

函数原型

pid_t getpid(void);		//获取子进程号
pid_t getppid(void);	//获取父进程号
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
/*
	pid_t getpid(void);		//获取子进程号
    pid_t getppid(void);	//获取父进程号
*/
int main()
{
	pid_t pid1 = getpid();	//获取子进程号
	pid_t pid2 = getppid();	//获取父进程号

	printf("子进程号:%d\n",pid1);	
	printf("父进程号:%d\n",pid2);	
	
	while(1);//这里方便查看设置死循环 可通过命令 ps -elf 查看进程号

	return 0;
}

运行结果

在这里插入图片描述

也可通过命令 ps -elf 查看进程号在这里插入图片描述

4. 僵尸进程和孤儿进程

  • 正常终止 、 异常终止。
  • 进程运行中需要消耗系统资源(内存、IO),进程终止得完全释放这些资源。

4.1 僵尸进程

  • 子进程比父进程先结束。这时候如果父进程还没有执行wait(), 那么子进程就是僵尸进程。
  • 因为当子进程结束后,内核会将子进程转为僵尸进程(zombie)来处理。会把子进程的大部分资源释放,给其他进程重新使用,唯一保留下的是内核的进程表中的一条记录。这个进程表中其中有 子进程的ID、终止状态、资源使用数据等信息。
  • 操作系统已经释放了大部分资源,剩下的其实是需要他的父进程去回收资源的,所以我们一般会用 wait()waitpid() 去回收子进程,这才是得到完全的释放。
  • 如果父进程执行了 wait() 那么这个时候就不需要子进程最后的一些信息,内核就会删除僵尸进程。
  • 一般也就是分为下面几种
    • 子进程先终止了,父进程没有调用 wait() 并且父进程还在,那么这就是僵尸进程,这个父一直不死,那么子进程就一直是僵尸进程。
    • 子进程先终止了,父进程调用了 wait() ,在子进程退出到在父进程调用 wait() 之前,这段也是僵尸进程了。不过最后父进程执行了 wait() 回收了子进程。
    • 子进程先终止了,如果父进程没有执行 wait() ,然后父进程结束进程了,那么子进程将会让 init 进程(祖先进程)接管,init 进程自动调用 wait()去回收。
  • 注意,如果子进程先终止,父进程还没终止,父进程也没调用 wait() ,子进程变成僵尸进程,你用SIGKILL 信号都是杀不死的。那么解决办法就是让它的父进程终止。下图是演示僵尸进程是杀不死的。
    在这里插入图片描述
    在这里插入图片描述

4.2 孤儿进程

  • 父进程比子进程先终止,子进程就变成了孤儿进程,通俗点说就是孩子它爸先不在了,它就成孤儿了。称为孤儿进程。
  • 子进程没有了爸爸,那 init进程 会收养它,init进程 则担起了回收子进程的责任。

5.如何避免僵尸进程

  • 主要想到的就是下面的几种。
    1. 调用wait()waitpid()去回收子进程。
    2. 利用 SIGCHLD 信号
    3. 在子进程终止后,如果没有回收,可以杀死它的父进程,让它的父进程结束。这也是一种办法。

函数详细介绍在下面

6. wait和waitpid介绍

用这两个需要包含头文件

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

6.1 wait() 函数

  • 原型:
pid_t wait(int *wstatus);
  • 参数

    • 保存被回收的进程退出时的状态,即我们用的return 或 exit 退出码。如果你不关心这个可以设置成 NULL
  • 返回值

    • 成功: 返回被回收的进程的 ID号
    • 失败: 返回 -1,errno 会置为 ECHILD

进程一旦调用了 wait() ,就会阻塞状态,由 wait() 分析当前进程的某个子进程是否已经退出 (因为一个进程可能有多个子进程),如果有一其中一个退出了,wait就会收集这个子进程的一些消息,然后销毁后返回信息,如果一直都没有子进程终止,那该进程会一直阻塞状态,直到有一个子进程终止为止。

可以用下方的宏来判断子进程的退出状态

宏定义描述
WIFEXITED(status)如果子进程正常结束,返回非0值
WEXITSTATUS(status)如果WIFEXITED非0,也就是正常退出,返回子进程退出码
WIFSIGNALED(status)子进程如果是因为捕获信号而终止,返回非0值
WTERMSIG(status)如果WIFSIGNALED非0,返回信号代码
WIFSTOPPED(status)如果子进程被暂停,返回非0值
WSTOPSIG(status)如果WIFSTOPPED非0,返回信号代码

6.2 waitpid() 函数

  • 原型
pid_t waitpid(pid_t pid, int *wstatus, int options);
  • 参数
    • pid:
      • pid 大于 0: 等待进程ID为pid 的子进程。
      • pid 等于 0: 等待与调用进程(父进程)同一个进程组(process group)的所有子进程。
      • pid 小于 -1: 等待进程组标识符与 pid 绝对值相等的所有子进程。
      • pid 等于 -1: 等待任意进程,和 wait(&status)和waitpid(-1,&status,0)一样
    • status:
      • 保存被回收进程的状态信息
    • options:
      • WNOHANG

        • 如果参数pid所指的子进程没有结束,则立马返回0,不会阻塞。
        • 如果参数pid所指的子进程没有,则waitpid 报错,错误号为ECHILD 通过perror()输出提示 No child processes
      • WUNTRACED

        • 除了返回终止子进程的信息外,还返回因信号而停止的子进程信息
      • WCONTINUED

        • 返回因收到 SIGCONT 信号而恢复执行的已停止子进程的状态信息。

6.3 wait和waitpid中的wstatus

wait和waitpid 中的 wstatus 可以以下区分子进程事件

  • 子进程是用exit() 或 _exit() 结束,并指定整数值退出,如子进程用exit(1);结束,那么wstatus 就是 1
  • 子进程收到未处理的信号而结束
  • 子进程因为信号而停止,并以WUNTRACED标志调用了waitpid()
  • 子进程收到信号 SIGCONT 而恢复运行,并以WCONTINUED标志调用了 waitpid()。

6.4 SIGCHLD信号

  • 上面有讲到回收子进程办法有 waitwaitpid 回收,如果子进程还没结束,会进行阻塞。(当然waitpid也可以设置不阻塞,就不断的去检测子进程是否结束) ,阻塞就啥也干不了,轮询也消耗cpu资源。还有一种办法就是 通过 SIGCHLD 信号去处理。
  • 子进程结束,系统都会向其父进程发送 SIGCHLD 信号
  • 在父进程代码中加上代码 signal(SIGCHLD, SIG_IGN); 表示告诉内核对子进程的结束我不关心,子进程结束就结束,我不管。

7. 进程是否有上限呢?

  • 在 32 位平台中, pid_max 文件的最大值为 32768。
  • 在 64 位平台中,该文件的最大值可以高达到 2^22 (4194304)400多万个。
  • 系统特有的 /proc/sys/kernel/pid_max 文件来进行调整(其值=最大进程号+1)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皮卡丘吉尔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值