Linux进程

目录

查看进程

进程状态

运行状态

睡眠状态

磁盘休眠状态

停止状态

死亡状态

僵死状态

孤儿进程

进程优先级

环境变量

PATH

​编辑

进程地址空间

进程创建

进程终止​​​​​​​

进程等待

进程程序替换

简易shell实现

获取命令行

解析命令行

建立子进程、替换子进程、父进程等待退出


就像各种管理系统一样,进程需要像被管理的对象一样先描述成具体的数据结构再进行组织,因此,进程分为内核数据结构以及进程的代码和数据。而内核数据结构中保存的便是进程属性的集合,该内核数据结构称为PCB(process control block),被保存在内存中。在Linux操作系统中PCB的结构体名称是task struct。

而task struct 中包含

标示符: 描述本进程的唯一标示符,用来区别其他进程。

状态: 任务状态,退出代码,退出信号等。

优先级: 相对于其他进程的优先级。

程序计数器: 程序中即将被执行的下一条指令的地址。

内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。

I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。

记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

其他信息
 

查看进程

我们可以通过ls /proc、ps、top命令来查看进程,例如ps -axj

当然,我们也可以通过管道来进行查看某些进程

例如我们新增一个程序

由于我们需要在该程序执行的过程中查看它的属性,因此我们在这里写了一个死循环来保证该进程能够一直执行

在创建好进程之后,我们可以新建一个窗口来查询该进程

 我们也可以将头部的属性名称显示出来

其中PID表示该进程的ID,PPID表示其父进程的ID,STAT表示进程状态。

我们也可以通过程序代码来查看该进程和父进程的id

#include <sys/types.h>
#include <unistd.h>pid_t getpid(void);
pid_t getppid(void);


进程状态

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

在Linux源码中,包括以上6种状态(T和t可以归为一类)

运行状态

R表示运行状态,当进程在内存中运行或是处于内存的运行队列的时候,便是运行状态。

例如这样一个死循环

该进程的状态便是R状态。

睡眠状态

而当我们等待非CPU资源就绪的时候,称为阻塞状态,这里的非CPU资源就包括输入设备,储存器,输出设备等,例如当我们进行scanf或是printf的时候,就需要去等待输入设备或输出设备。

 

而当内存不足的时候,操作系统会通过适当的置换进程的代码和数据到磁盘(swap 分区)来解决这个问题,而被置换的进程所处的状态就叫做挂起状态。

而这两种状态都是在等待事件的完成(阻塞状态等待非cpu资源,而挂起状态等待自己的代码和数据被置换回内存继续运行),因此都被称为睡眠状态(S)也称作可中断睡眠。

磁盘休眠状态

当服务器压力过大的时候,操作系统会通过一些手段来杀掉一些进程,来节省空间。

而当进程与磁盘进行大量数据的IO时,该进程会处于磁盘休眠状态(D状态),该进程不可被中断,不可被被动唤醒,也不会被上面所说的过程被杀掉,只能等待IO的结束或是关机重启。

dd命令可以模拟该状态。

停止状态

顾名思义,就是进程被暂停了,我们可以通过发送SIGSTOP信号(kill -19)来停止进程,也可以通过发送SIGCONT信号(kill -18)来让进程继续进行,同时,在调试的过程中进程也处于停止状态。

死亡状态

具有瞬时性,无法查看。

僵死状态

当一个进程退出时,不逊于操作系统释放,该等待被父进程 检测的状态称为僵死状态。

fork函数

#include <sys/types.h>
#include <unistd.h>pid_t fork(void);

创建子进程,共享父进程后续的代码,创建失败返回-1,成功则给子进程返回0,给父进程返回子进程的PID。

我们可以利用这个函数来实现一下僵尸进程

可以看到,子进程sleep  3秒后该进程就会退出,而父进程处于死循环,因此,在查看进程状态时,三秒后子进程的状态变成了僵死状态 。

若是一直处于僵尸进程,该进程的PCB会被操作系统一直维护,从而导致内存泄漏,而为了避免内存泄漏,就需要父进程进行进程等待(后面讲)。

孤儿进程

当父进程退出后,未退出的子进程就被称作孤儿进程,该进程会被一号进程init回收。


进程优先级

  通过ps -la命令,我们可以看到PRI和NI这两个属性,分别代表了该进程的优先级和nice值。

优先级的值越小,优先级别就越高。

PRI表示原本的优先级加上nice值后得到的值,nice值可以被我们修改,修改范围为-20到19,具体操作为top、r、PID、nice值。

 

 

其他概念

竞争性:进程的数目远大于CPU资源,因此进程之间具有竞争性,为了高效完成任务,需要优先级来合理安排。

独立性:多进程之间需要独享资源,互不干扰(包括父子进程之间)。

并行:多进程在多CPU下同时运行。

并发:多个进程在一个CPU下采用进程切换的方式使得各个进程得以推进。

在进程执行的某些时刻,内核可以决定抢占当前进程(当前进程出让给另一个进程),并重新开始一个先前被抢占了的进程,这种决策叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行时,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来控制转移到新的进程。

寄存器中的所存储的临时数据称为进程的上下文,寄存器只有一份,而上下文有多份,在进程运行期间上下文不能被丢弃。

上下文切换主要分为三步,(1). 保存当前进程的上下文        (2). 恢复某个先前被抢占的进程被保存的上下文(保证该进程被切换回来后,可以按照之前的逻辑继续运行)        (3). 将控制传递给这个新恢复的进程。


环境变量

环境变量指在操作系统中用来指定操作系统运行环境的一些参数。

常见环境变量:

PATH:指定命令的搜索路径

HOME:指定用户的主工作目录

SHELL:当前Shell,通常为/bin/bash

我们可以通过echo $NAME查看环境变量

PATH

当我们执行系统的命令时,我们不需要使用路径,而当我们执行自己的程序时,却必须带路径,这是由于系统的命令所处的路径全都在PATH环境变量中。

我们可以通过export命令来自己设置环境变量的值,因此我们可以通过export PATH=$PATH:添加程序的地址来新增一条地址 。

除此之外,env指令为显示所有环境变量,unset指令为清除环境变量,set命令为显示本地定义的shell变量和环境变量

除了使用命令之外,我们还可以通过代码来获取环境变量

第一种方法,我们可以使用main的参数来获取(第三个参数 char *env[])

而第二种方法,我们可以通过第三方变量environ来获取

 而这两个环境表(env、environ)都是字符指针数组,每个指针指向每个环境变量字符串。而结尾为NULL。

我们还可以通过getenv来获取单个环境变量

 子进程继承父进程所有的环境变量


进程地址空间

在之前,我们了解过内存的空间布局

而当我们使用fork创建父子进程时

 可以看到,在fork创建了子进程后,子进程对变量a进行了修改,这导致了父进程和子进程的变量a的地址相同,但是a的值却是不相同的。

这是因为,在Linux下,进程的地址都是虚拟地址,当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。因此,父子进程即使地址相同,但所映射的物理地址并不相同,所存储的数据也就不相同。而这些虚拟地址就被称为虚拟地址空间。

每一个进程都有自己的地址空间,操作系统将所有的空间先描述再组织,因此地址空间也是一种内核数据结构。

首先,任务结构(PCB / task_struct)中的一个条目指向mm_struct,它描述了虚拟内存的当前状态。而其中重要的是pgb和mmap两个字段,mmap指向一个vm_area_structs(区域结构)的链表,其中每个结点都描述了当前虚拟地址空间的一个区域(代码区、数据区等)。

Linux内存映射机制_灵魂构造师的博客-CSDN博客_linux内存映射机制

 结点中各个字段的含义:

vm_start:指向该区域起始处     vm_end:指向该区域结束处      vm_prot:描述该区域的读写权限

vm_flags:描述该区域内的页面是与其他进程共享还是私有        vm_next:指向下一个结点

而pgb指向第一级页表的基址。

当我们想要读写进程地址空间中的数据等,就需要通过页表来将虚拟地址映射成物理地址。而每一个进程的页表,都会将其虚拟地址映射到物理内存的不同区域,这也就使得进程之间在内存方面不会互相干扰,具有独立性。

有了进程地址空间,首先,当我们进行非法的访问或映射时,操作系统都能够识别到,并终止该进程。因此也能够保护物理地址中其他进程的数据等。

其次,通过页表的映射,数据可以被加载到任意位置,也就能实现内存管理模块和进程管理模块的解耦合。同时,由于页表的存在,我们也可以将内存有序化。

同时,通过进程地址空间,我们可以在申请内存的时候只申请虚拟地址,暂时不分配内存地址,当该块空间被访问时再去申请内存地址,来通过延迟分配提高效率。


进程创建

在前面进程状态的时候,我们提到过fork函数的使用。

当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,为了给其创建虚拟内存,创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,更新页表条目指向这个新的副本,然后恢复这个页面的可写权限。

而子进程继承的是父进程的mm_struct、区域结构和页表的原样副本。因此代码也是全部继承的。但也同时继承了父进程的上下文,因此,子进程也随同父进程执行fork之后的代码,而不会去执行fork之前的代码。

而系统采用写时拷贝的原因也很简单,首先,写时拷贝能够在数据被写入的时候再进行申请,而没有被写入的数据父子进程公用空间,因此能够高效的使用内存。其次,系统无法预知哪些数据之后会被写入,因此我们也就无法提前将它们在创建子进程时拷贝。

同时,我们之前提到过,fork返回值为-1时表示进程创建失败。主要原因是系统中的进程太多或是实际用户的进程数超过了限制。


进程终止

当进程终止后,操作系统要释放进程申请的相关内核数据结构和对应的代码和数据。

而进程终止的情况分为三种

代码运行完毕,结果正确

代码运行完毕,结果不正确

代码异常终止

而进程退出的方法分为

正常终止:main函数返回(return)、exit函数、和_exit函数

#include<stdilb.h>void exit(int status);
#include<unistd.h>void _exit(int status);

main函数返回实质上也是调用的exit函数,而exit与_exit函数的区别在于,exit会调用_exit,但在调用之前,exit会执行用户定义的清理函数以及冲刷缓冲区、关闭流等。

而异常终止是出现在程序崩溃时,此时退出码没有意义

我们可以使用echo $?来获取最近进程的退出码

 

 

可以看到,_exit并不会冲刷出缓存区里的"Hello Linux"语句 

可以通过strerror函数来将错误码转换为错误信息

#include <string.h>
#include <errno.h>char * strerror ( int errnum );

#include<stdio.h>
#include<string.h>
#include<errno.h>int main(){int index=1;for(;index<140;index++){printf("errnum[%d]:%s\n", index, strerror(index));}return 0;
}


进程等待

在前面的进程状态中,我们提到过僵死状态,就是子进程退出而父进程还没将其回收的状态。而我们可以使用wait或是waitpid函数来将子进程回收。同时,这两个函数还可以获取子进程的退出信息来判断子进程退出时的状况。

#include<sys/types.h>
#include<sys/wait.h>pid_t wait(int *status);pid_t waitpid(pid_t pid, int *status, int options);

而 wait(&status) 等价于 waitpid(-1, &status, 0)

waitpid的三个参数中,pid表示需要等待的子进程的pid,若pid为-1,表示等待任意子进程。

status表示等待到的子进程的退出状态,我们在这里只看其低16位,其中高8位表示退出码,低7位表示终止信号,若终止信号不为0,表示子进程异常终止,此时退出码无效。而中间1位表示core dump标志。

我们可以通过位操作来获取退出码和终止信号,同时,也可以利用现有的函数来获取

WIFEXITED(status)//若为正常终止子进程返回的状态,则为真(终止信号为0)
WEXITSTATUS(status)//若正常退出,则提取子进程退出码
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>int main(){int id=fork();if(id==0){sleep(3);                                                                                                                                   exit(1);}else{int status=0, ret=0;ret=waitpid(-1,&status,0);if(ret>0&&WIFEXITED(status))printf("child exit code:%d\n", WEXITSTATUS(status));else if(ret>0)printf("sig code:%d\n", status&0x7f);}return 0;
}

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>int main(){int id=fork();if(id==0){printf("child pid:%d\n", getpid());while(1);exit(1);}else{int status=0, ret=0;ret=waitpid(-1,&status,0);if(ret>0&&WIFEXITED(status))printf("child exit code:%d\n", WEXITSTATUS(status));else if(ret>0)printf("sig code:%d\n", status&0x7f);}return 0;
}

 

 options:WNOHANG表示非阻塞等待(define WHOHANG 1)若子进程没有结束,则返回0。

                0表示阻塞等待,一直等待,直到子进程结束

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>int main(){int id=fork();if(id==0){sleep(3);exit(1);}else{int status=0, ret=0;while(1){ret=waitpid(-1,&status,WNOHANG);if(ret==0){printf("waiting\n");sleep(1);}else{if(WIFEXITED(status))printf("child exit code:%d\n", WEXITSTATUS(status));else printf("sig code:%d\n", status&0x7f);break;}}}return 0;
}


进程程序替换

我们可以用exec函数来将进程的程序替换成另一个程序。进程程序替换只会替换该进程的用户空间代码和数据,而不会改变内核数据结构中进程的属性等。

exec函数包含六个函数

#include<unisted.h>int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char* const argv[]);
int execvp(const char *file, char* const argv[]);
int execve(const char *file, char* const argv[]);

 其中,path代表替换程序的路径+文件名,file代表文件名(需要在环境变量PATH中),arg表示命令行的参数,以单个参数的形式存在可变参数列表中,以NULL结尾, argv表示命令行的参数,以数组形式作为参数,envp表示环境变量数组。而前五个函数最终都会调用第六个函数execve

这些函数在出错时返回值为-1,而成功时没有返回值。

通常,我们通过fork创建子进程来进行程序替换,这样的话,不会影响父进程,使父进程能够聚焦在读取数据、解析数据、指派进程执行代码等行为上。

在进行进程程序替换时,子进程进行了大量的写入,因此在这时发生了写时拷贝,将父子进程的数据和代码分离。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main(){int id=0, ret=0, status=0;id=fork();if(id==0){execl("/bin/ls", "ls", "-a", "-l", NULL);exit(0);}else{ret=wait(&status);}return 0;
}


简易shell实现

 shell简单来说,主要分为五个过程

获取命令行

解析命令行

建立一个子进程

替换子进程

父进程等待子进程退出

首先,在获取命令行之前,前面还有

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
int main(){int id=0, ret=0, status=0;while(1){printf("[szt@localhost myshell]$ ");fflush(stdout);//没有回车,需要冲刷缓存区}
}

获取命令行

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>//命令行字符串
char cmd_line[1024];int main(){int id=0, ret=0, status=0;while(1){printf("[szt@localhost myshell]$ ");fflush(stdout);memset(cmd_line, '\0', sizeof cmd_line);if(fgets(cmd_line, sizeof cmd_line, stdin)==NULL)continue;}return 0;
}

解析命令行

在我们学习进程替换时,我们知道命令行是以字符串数组的形式作为参数,因此我们需要将命令行字符串以空格做分隔形成字符串数组。而想要实现这一功能我们又现成的函数strtok

#include <string.h>char * strtok ( char * str, const char * delimiters );
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>//命令行字符串
char cmd_line[1024];
//命令行参数字符串数组
char *g_argv[32];int main(){int id=0, ret=0, status=0;while(1){printf("[szt@localhost myshell]$ ");fflush(stdout);//获取命令行memset(cmd_line, '\0', sizeof cmd_line);if(fgets(cmd_line, sizeof cmd_line, stdin)==NULL)continue;//解析命令行//去除命令行字符串尾部的回车cmd_line[strlen(cmd_line)-1]='\0';g_argv[0]=strtok(cmd_line, " ");int index=0;while(g_argv[++index]=strtok(NULL, " ")) ;}return 0;
}

建立子进程、替换子进程、父进程等待退出

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>//命令行字符串
char cmd_line[1024];
//命令行参数字符串数组
char *g_argv[32];int main(){int id=0, ret=0, status=0;while(1){printf("[szt@localhost myshell]$ ");fflush(stdout);//获取命令行memset(cmd_line, '\0', sizeof cmd_line);if(fgets(cmd_line, sizeof cmd_line, stdin)==NULL)continue;//解析命令行//去除命令行字符串尾部的回车cmd_line[strlen(cmd_line)-1]='\0';g_argv[0]=strtok(cmd_line, " ");int index=0;while(g_argv[++index]=strtok(NULL, " ")) ;id=fork();if(id==0){execvp(g_argv[0], g_argv);printf("-bash: %s: command not found\n", g_argv[0]);exit(1);//当替换失败时,才会执行子进程原本后来的代码}else{ret=wait(&status);}}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/13848.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

应用层:客户-服务器方式(C/S)、对等方式(P2P)

1.应用层&#xff1a;客户-服务器方式和对等方式 笔记来源&#xff1a; 湖科大教书匠&#xff1a;客户-服务器方式和对等方式 声明&#xff1a;该学习笔记来自湖科大教书匠&#xff0c;笔记仅做学习参考 开发一种新的网络应用首先要考虑的问题就是网络应用程序在各种端系统上的…

权限管理系统后端实现1-SpringSecurity执行原理概述

spring security的简单原理&#xff1a; SpringSecurity有很多很多的拦截器&#xff0c;在执行流程里面主要有两个核心的拦截器 1&#xff0c;登陆验证拦截器AuthenticationProcessingFilter 2&#xff0c;资源管理拦截器AbstractSecurityInterceptor 但拦截器里面的实现需要…

基于YOLO的3D人脸关键点检测方案

目录 前言一、任务列表二、3D人脸关键点数据H3WB2.下载方法3.任务4.评估5.使用许可 3DFAWAFLW2000-3D 三、3D关键点的Z维度信息1.基于3DMM模型的方法2.H3WB 四、当前SOTA的方法1.方法1 五、我们的解决方法1.数据转为YOLO格式2.修改YOLO8Pose的入口出口3.开始训练&#xff0c;并…

网络的构成要素【图解TCP/IP(笔记七)】

文章目录 网络的构成要素通信媒介与数据链路网卡中继器网桥/2层交换机路由器/3层交换机4&#xff5e;7层交换机网关各种设备及其对应网络分层概览 网络的构成要素 通信媒介与数据链路 计算机之间通过电缆相互连接。电缆可以分为很多种&#xff0c;包括双绞线电缆、光纤电缆、同…

Openlayers实战:drawstart,drawend 绘制交互应用示例

Openlayers地图中,绘制一个多边形是非常见的一个应用,涉及到交互会在绘制开始 drawstart 和绘制结束drawend时,通常会在绘制完成后取消继续绘制,然后提出feature的一些信息。 效果图 源代码 /* * @Author: 大剑师兰特(xiaozhuanlan),还是大剑师兰特(CSDN) * @此源代…

机器学习基础之《特征工程(1)—数据集》

一、数据集 1、目标 知道数据集分为训练集和测试集 会使用sklearn的数据集 2、可用数据集 公司内部&#xff0c;比如百度、微博 数据接口&#xff0c;花钱 政府拥有的数据集 3、在学习阶段用到的数据集 scikit-learn特点&#xff1a; &#xff08;1&#xff09;数据量较小 &…

创建数据库Market、Team,按要求完成指定操作

创建数据库Market&#xff0c;在Market中创建数据表customers&#xff0c;customers表结构如表4.6所示&#xff0c;按要求进行操作。 代码如下&#xff1a; #(1&#xff09;创建数据库Market mysql> create database Market; Query OK, 1 row affected (0.00 sec)mysql>…

瓴羊QuickBI数据门户帮助企业高效管理和展示数据,使其更加明确易懂

随着信息技术时代的到来&#xff0c;越来越多的企业意识到商业信息是其最宝贵的资产之一。对于获取商业信息&#xff0c;需要专业的数据分析。因此&#xff0c;商业智能BI工具&#xff0c;如瓴羊QuickBI已经成为企业信息化中必不可少的工具。它拥有卓越的数据管理和展示功能&am…

【STM32智能车】小车状态

【STM32智能车】小车状态 搭建智能车 65MM轮径小车所选材料安装说明直行测试智能车可能存在的状态 智能车功能丰富&#xff0c;我们从最基础的开始&#xff0c;先来搭建一个智能车吧~。 搭建智能车 我们之前用了一个测试板子去学习调试电机&#xff0c;是时候拼装一个简单的车来…

学校公寓管理系统/基于微信小程序的学校公寓管理系统

摘 要 社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个学生的使用。手机具有便利性&#xff0c;速度快&#xff0c;效率高&#xff0c;成本低等优点。 因此&#xff0c;构建符合自己要求的操作…

优化器学习

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、SGD&#xff08;随机梯度下降算法&#xff09;二、Momentum三、AdaGrad四、Adam算法 前言 最优化是应用数学的一个分支&#xff0c;主要研究在特地情况下函…

不同路径(力扣)动态规划 JAVA

一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f; 示例 1&a…