【Linux】进程的管理

1 进程的概念

课本概念:加载到内存中的程序叫进程,例如程序的一个执行实例,正在执行的程序等。
内核角度:担当分配系统资源(CPU时间,内存)的实体。

2 进程的管理

先描述进程 — PCB
再组织进程 — 双向链表形式存储在内核中

2.1 描述进程–PCB

PCB
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。课本上称之为PCB(process control block);
Linux操作系统下的PCB是 task_struct,task_struct 是Linux内核的一种数据结构,会被装载到RAM(内存)里并且包含着进程的信息。

  • 进程的属性:

标识符: 描述当前进程的唯一标识符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
上下文数据: 进程执行时处理器的寄存器中的数据
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
I/ O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息…

程序和进程
程序的本质是放在磁盘上的可执行文件(.exe文件),就是一个文件;根据冯诺依曼体系,软件运行要加载到内存中,而进程则是将程序加载到内存当中,并且由操作系统进行管理,生成一个描述自身性质的数据结构(PCB),由内核数据结构和进程对应的磁盘代码两者共同组成“进程”。

2.2 获取进程标识符(系统调用

  • 父进程id(PPID)的获取:通过 pid_t getppid(void) 可获取
    一般给程序分配后就不会再发生变化,除非重启电脑
    命令行上启动的进程,一般它的父进程没有特殊情况的话都是bash

  • 子进程id(PID)的获取:通过pid_t getpid(void) 可获取
    程序每次运行都会变化,是由父进程创建的子进程

下面用一段代码直接输出父子进程id看一下:

  1 # include <stdio.h>
  2 # include <sys/types.h>  //获取进程PID需要的头文件,调用相应函数
  3 # include <unistd.h>
  4 
  5 int main()
  6 {
  7     printf("ppid:%d\n",getppid());
  8     printf("pid:%d\n",getpid());
  9     return 0;
 10 }

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

2.3 查看进程状态

ps aux | ps axj

进程的状态:R(运行) S/D(睡眠) T(停止) Z(僵尸)

Z-僵尸进程:子进程不按规则退出,使得父进程无法回收资源(PCB)。
僵尸进程总是会等待父进程读取其退出状态,否则会出现内存泄漏。
孤儿进程:父进程先退出,子进程便变成“孤儿”,会被操作系统领养。

2.4 进程优先级

CPU 资源资源分配的先后顺序 = PRI + NI

查看优先级: ps -l
更改优先级:renice ( 进程pid )

UID:执行者身份
PID:子进程 id
PPID:父进程 id
PRI:被执行者优先级,值越小越容易被执行
NI(nice值):优先级的修正值,决定优先级

2.5 创建进程 – fork() 函数

fork()作用:创建子进程;

如下图所示
在这里插入图片描述
由运行结果可知:C程序一开始,就会产生一个进程28287其父进程为bash,当执行过fork()函数后即创建子进程完成,此时父进程28287和子进程28288是共存的,它们俩会一起向下执行C程序的代码,因此printf被执行两次就会输出两行。

fork()返回值探索

  • 若我们对fork()的返回值感兴趣的话,就会发现 新大陆
    如下图所示,fork()函数竟然有两个返回值,这完全超出了之前的认知
    在这里插入图片描述
    根据当前结果看出,需要通过if进行分流才能准确的输出两个返回值

  • 返回值1:给父进程返回子进程的pid。(方便管理子进程)

  • 返回值2:给子进程返回0。(只有一个父进程故无需访问)

    fork() 调用失败 会返回-1,fork失败一般有两个原因:
    1)系统中有太多的进程
    2)实际用户的进程数超过了限制

验证返回值的代码演示:

#include <stdio.h>
#include <unistd.h>
 
int main(){
        pid_t pid;
        printf("pid = %d\n",getpid());
        pid = fork();
        if(pid > 0 ){
                printf("this is father pid:%d\n",getpid());
        }else if(pid == 0){
                printf("this is child pid:%d\n",getpid());
        }
        return 0;
}

执行结果:
在这里插入图片描述

fork()创建子进程的实现

fork()工作流程:

1.在fork()前父进程独立执行;


2.执行到fork()时,操作系统为新进程在进程表中分配一个空项,即内核会分配新的内存块和内核数据结构给子进程;
为子进程赋一个唯一的进程标识符;
做一个父进程上下文的逻辑副本,不包括共享内存区;增加父进程拥有的所有文件的计数器,以表示有一个另外的进程现在也拥有这些文件;
把子进程置为就绪态;


3.fork()返回,向父进程返回子进程的进程号;对子进程返回零。
4.fork()结束,开始调度器调度;

创建成功后,父子进程间,代码共享,数据私有(写时拷贝)
但父子进程的运行顺序只与调度器有关,我们无法控制。

父子进程间的数据拷贝

旧版本的Linux拷贝方法:全拷贝,有啥拷啥
新版本的Linux拷贝方法:写实拷贝,写的时候才拷贝(改原文件哪里了,就拷贝哪里,没改的地方,实施共享原则);

  • 写实拷贝是一种推迟或者免除拷贝的技术。
    注:写时拷贝技术是以“页”为单位,因此哪怕这个页中只有一个字节被修改了,我们也需要将整个页面都复制出来一份。
    如图:子进程创建成功后,内核并不会立刻复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。当原文件的0页和1页被修改时,操作系统就拷贝0和1的内存并写入新内容;3页和4页无写入请求,故仍与父进程共享数据。

在这里插入图片描述

趣味问答

下面这段代码,一共会输出几个 ‘=’??

#include <stdio.h>
#include <unistd.h>

int main(){
	for(int i = 0;i<2;++i){
		fork();
		printf("=");
	}
	return 0;
}

答案:8个。由于第一次创建的时候 ‘ = ’在缓冲区,所以在拷贝地址空间的时候也把缓冲区的内容复制给了第二个进程,接下来两个进程又分别创建一个进程,创建的进程都带有 ‘=’ ,然后四个进程分别往缓冲区里输入 ‘=’,最后刷新缓冲区;特别注意的就是:缓冲区也是内存,在进程创建的时候也会被拷贝到另一个进程中;

3 进程的其他操作

3.1 查看进程id

linux命令直接查看:ls / proc /

在这里插入图片描述

如上图所示,在Linux中proc是内存级目录,即ls可直接查看;
其中数字开头就是进程的pid,同时一个进程也可以当做文件来看待,如下图
在这里插入图片描述

结束进程:kill 9 指定进程id 可以结束进程,当然ctrl+c也可以直接结束掉

进程结束后,再次查看该进程即不存在,指定进程id查看会提示进程不存在;

进程在运行的时候本质是在读取进程内部的代码后再在内部执行,从启动到终止中间可能会有一段很长的时间,这个进程就具备了动态属性。
也就是说,进程在调度运行的时候,进程就具有动态属性。

3.2 进程终止

用echo $? 可查看最近的进程的退出码

退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

退出方法
1)异常退出

ctrl + c 信号终止

2)正常退出

main()返回值=当前进程的退出码。return 0 表示退出无误
exit(int status):当前进程刷新缓冲区并退出且退出码为status
_exit(a):直接终止进程,不刷新缓冲区
在这里插入图片描述

3.3 进程等待

waitpid(-1, *status ,0)=wait(*status)

等待的必要性:获取子进程退出信息,防止内存泄漏

等待方法
1)pid_t wait(int *status);

返回值:成功返回被等待进程的pid,失败返回-1
参数:获得子进程的退出状态,不关心则设置成NULL

2)pid_t waitpid(pid_t pid,int *status,int option);

返回值:成功返回子进程的id 或者0,调用失败返回-1
参数:pid =-1,等待任一子进程,与wait()等效;pid>0等待一个与pid相等的进程
status:WIFEXITED 查看是否正常退出;WEXITSTATUS 提取子进程退出码
option:WNOHANG 阻塞式等待

3.4 进程程序替换

当前进程调用exec函数,将替换程序加载到内存中,直接在物理内存中进行替换
在这里插入图片描述

  • 替换函数:exec函数无返回值,用exit(1)结尾
    只有出错时返回值为-1,成功时无返回值

exec 函数使用
int execl(const char* path,const char* arg,…);
int execlp(const charfile,const char arg,…);
int execle(const char
path,const char
arg,…,char* const envp[]);
int execv(const charpath,const char const argv[]);
int execvp(const charfile,char const argv[]);
int execve(const charpath,char const argv[],char* constenvp[]);
最常用的就是封装的execve!!

l(list):参数采用列表
v(vector):参数采用数组
p(path) :自动搜索环境变量PATH
e(envp):自己维护环境变量

  • 替换函数间关系
    在这里插入图片描述
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值