Linux——进程初识(二)

1. 对当前目录创建文件的理解

我们知道在创建一个文件时,它会被默认创建到当前目录下,那么它是如何知道当前目录的呢?

对于下面这样一段代码

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

int main()
{
    fopen("tmp.txt", "w");
    while (1)
    {
        printf("这是一个进程\n");
        sleep(1);
    }

    return 0;
}

在它被加载成为一个进程时,我们查看相应的PID有

在Linux中所有进程是被存放在一个/proc目录中的,即

我们找到对应的PID就能进入并查看该进程,进入后发现

可以看到,在进程中有一个cwd文件,即current work dir(当前工作目录),在代码中使用fopen向磁盘中写入文件tmp.txt时,会自动的将cwd中的路径拼接到它的前面

2. 进程标识符

①PID

PID是进程标识符(Process Identifier)的缩写,它是一个唯一标识符,用于标识正在运行的每个进程。每个进程在系统中都有一个唯一的PID,可以通过PID来识别和管理进程。PID是一个非负整数,通常在系统启动时自动分配给进程,并且在一个给定的时间内是唯一的。

以以下代码为例

编译后运行有

有了标识符之后我们可以通过使用对应进程的PID使用kill命令来干掉该进程,即

kill -9 12489

那么我们如何知道当前进程的PID呢?

首先我们要知道PID是存放在task_struct中的,在我们使用ps命令时,它的本质就是遍历一遍task_struct链表,那么我们怎么获取呢——Linux肯定是不希望我们直接通过使用域访问符.来取得PID的,因此它提供了一个系统调用的接口即函数getpid(),它的手册如下

我们多运行几次后可以发现

对PID来说,PID只会保证当前运行期间有效,所以在不同的运行期间,其会不断变化

②PPID

PPID指的是父进程的PID,即父进程的进程ID号。与PID类似,要获取PPID我们也可以使用对应函数getppid(),其手册如下

在上面的多次运行中我们可以发现在不同运行期间PPID一般不变,我们查看可以发现

PID为6116的只有一个——bash,我们之前提到过,对于输入的命令,系统会单独创建一个bash来处理输入的命令,这样就能做到在输入命令时,会将其作为bash的子进程运行。而在断开主机重连后可以看到

此时PPID发生了变化,这是因为在登录到主机时,系统会单独新创建一个bash。

3. 创建进程——fork

我们以下面的代码为例

对其编译运行后我们可以使用

while :; do ps ajx | head -1 ; ps ajx | grep mycode | grep -v grep;sleep 1;done

来不断查看与mycode相关进程的状态

我们查看fork手册有

可以看到在手册中提到fork会返回两个值,返回id==0时,标识其为子进程,id>0时,标识其为父进程,而在运行结果中我们可以看到,父进程就是当前进程,子进程是新分支。至此,我们对于创建一个新进程有两种方法,其中一个就是使用./文件的方式在指令层面创建一个进程,另外一个就是使用fork函数在代码层面创建一个进程。其实在调用fork函数之后,会产生两个执行流。

在这里我们可以提出几个问题:

1. 为什么fork要给子进程返回0, 给父进程返回PID?

首先我们要知道,fork返回不同的值是为了让不同的执行流去执行不同的代码块,因为fork之后的代码是父子进程共享的,因此控制if等条件即可控制不同执行流。给子进程返回0只是一个标记,标志着子进程创建成功,而给父进程返回PID是因为对于一个父进程,其可能会有多个子进程,拿到子进程PID是为了标识唯一性。

2. fork函数是如何做到返回两次的?

首先我们要知道,创建一个子进程对于Linux来说就是创建一个新的task_struct,即只需要将原来的父进程task_struct拷贝一份,再对其中的部分属性做修改(如:PID,PPID等)即可,而在fork后父子进程访问这之后的同一份代码,因为代码不可修改,但是由于数据可能被修改,因此不能让父子进程共享同一份数据,那么就该让子进程拷贝一份父进程的数据,但是如果拷贝之后没有对数据进行修改那么又会导致资源的浪费,因此Linux规定在子进程尝试修改数据时,操作系统会为其申请一份新空间(使用多少申请多少),子进程修改这份新空间的数据即可,这样的方式也被称为数据层面的写实拷贝。

而对于fork来说,他是一个函数其内部也有其自己的实现,其内部可能包含:1. 创建子进程task_struct; 2. 填充task_struct;3. 让它指向同一份代码;4. 使它可以被自由调度;......在完成了这一系列的任务之后,子进程已经被创建好了,此时由于父子进程共享同一份代码,到最后的return 语句时,父进程与子进程会各自返回一次数据。

3. 对于id变量,它是怎么做到拥有不同内容的?

在代码中可以看到,pid_t id = fork();这个id就是数据内容,在fork返回两次后,对于id来说发生了数据的写实拷贝。

在了解了进程的创建后,我们对于bash也有了一个新的认识,即它在使用的途中一定会调用fork函数,并用其来创建子进程(执行解释命令)。

4. 进程状态

①一般操作系统学科中的进程状态

1. 运行

这些task_struct已经准备好了,可以随时被调度,此时在队列中的状态称为运行态(R),一般来说在队列中是到了谁就执行谁。那么只要进程放到CPU中,是不是一定要执行完毕所有的内容,才能执行下一个进程呢(如while(1))?答案肯定是否定的,其实对于每个进程都具有一个属性——时间片,有了时间片后,在一段时间内,所有的进程代码都会执行(并发执行)。而在这个过程中,一定会有大量的把进程从cpu上放上与拿下的动作,我们将其称为进程切换。

2. 阻塞

当task_struct对应的数据代码需要从键盘中读取数据时,但是此时却没有输入时,这种状态就称为阻塞状态,此时该task_struct会被链入键盘的waitqueue中,如果下一个需要键盘输入的task_struct直接链入之后的队列即可。

3. 挂起

在阻塞状态时,如果操作系统内部资源不足时,为了保证操作系统维持正常状态而要省出资源,此时操作系统会将task_struct保留,将代码和数据放在外设中(换出),此时的进程状态为挂起,而在需要时会将代码和数据加载回来(换入)。

②Linux中的进程状态

在Linux中定义如下

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

以下面的代码为例

我将其运行后查看

此时的S+(此处的+表示前台运行,不能输入bash命令)表示处于S状态(即阻塞状态),这是因为cpu的运行速度太快,而显示屏运行速度相等较慢,因此有极大的可能性时处于S状态,而我们将代码修改一下

,即可发现

此时,由于不需要等待外设,因此一直处于运行态即R。

对于D状态,我们先举一个具体的例子,

若处于极端情况下时,进程被kill,磁盘写入数据失败时,反馈信息给进程时,进程却不见了,此时磁盘一般会选择丢失这部分数据,那么为了防止这种情况发生,我们只需要让进程在等待磁盘时,不能被杀掉即可,即将其设置为D状态,在磁盘写入完毕后再将其状态修改为S。由此,我们可以认识到S状态属于浅度睡眠,可以随时响应系统的调度,而D状态属于深度睡眠,它不会响应系统调度。

对于T状态,我们可以使用kill的命令来暂停进程,即

查看后,我们知道可以使用-19命令来发出暂停信号, 即

此时我们可以看到mycode处于暂停状态,而对于t状态,我们可以使用gdb来演示

可以看到,当我们使用断点停止在某一处时,此时mycode处于t状态。

对于X状态和Z状态,在一个进程死亡的时候,会先进入Z状态,其目的是需要维持相应的状态,直到被父进程读取到信息后,其状态才会转换成X(瞬时)。

我们以下面的代码举例

运行并监视有

可以看到,在子进程结束,父进程未结束后,子进程处于Z+状态<defunct>(失效的),我们将此状态称为僵尸状态,进程一般退出时,若父进程没有主动回收子进程信息,子进程会一直处于Z状态,这样就会导致资源会被一直占用,就有可能导致内存泄漏。

将代码修改一下

运行并监视有

可以看到,对于操作系统本身来说,若父进程先退出,其子进程的父进程会被修改为1号进程(即操作系统)。对于父进程为1的进程我们将其称为孤儿进程,该进程被操作系统所领养。那么为什么要被领养呢?因为孤儿进程未来也要退出,也需要被释放,而操作系统本身具有回收功能。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++大作业——学生选课系统优化版

C大作业——学生选课系统优化版 前言1.学生类和课程类的实现2.输入输出流重载的实现3.增删改查的实现4.多级菜单的实现5.选课和退选的实现5.完整代码 前言 本文是对本人之前写过的一个学生选课系统的优化&#xff0c;整体上的逻辑是和我上一篇博客一样的&#xff08;链接在此&a…

这次,数据泄露的目标受害者指向了---救护车服务公司

已停业的救护车服务遭到勒索软件攻击导致近百万人受到威胁&#xff01; 此次数据泄露的目标受害者是法伦救护车服务公司&#xff0c;该公司是Transformative Healthcare的子公司。ALPHV勒索软件团伙声称对2023年4月下旬对Transformative Healthcare的攻击负责&#xff0c;并导…

3dmax全景图用什么渲染 全景图云渲染使用教程

在给客户展示设计概念时&#xff0c;应用3ds Max创建的全景图是一个高效直观的方法。这种方式不仅可以全方位地呈现整个空间&#xff0c;让客户沉浸式地感受设计师的创意&#xff0c;而且在展现大型空间设计&#xff0c;如展览馆或者会议室等&#xff0c;效果尤其显著&#xff…

如何在anaconda里安装basemap和pyproj库

当直接使用conda命令进行安装basemap和pyproj库时&#xff0c;会出现版本不对应的报错问题(如下图)&#xff0c;所以此篇博客用以展示如何安装basemap和pyproj库 题主默认使用的anaconda源已经切换成了清华大学源&#xff0c;但是仍然会出现报错&#xff0c;所以不是源的问题&a…

什么是向量数据库

什么是向量数据库 向量数据库是一种应用在高效存储和查询高维向量的数据库。在传统的OLTP和OLAP数据库中&#xff08;如上图所示&#xff09;&#xff0c;数据按行和列组织&#xff08;这些称为表&#xff09;&#xff0c;并根据这些列中的值执行查询。然而&#xff0c;在某些应…

ASP.NET Core AOT

Native AOT 最初在 .NET 7 中引入&#xff0c;在即将发布的 .NET 8 版本中可以与 ASP.NET Core 一起使用。在这篇文章中&#xff0c;我们从总体角度审视其优点和缺点&#xff0c;并进行测量以量化不同平台上的改进。 源代码&#xff1a;https://download.csdn.net/download/he…

华清远见作业第二十天——IO(第三天)

思维导图&#xff1a; 使用标准IO完成两个文件的拷贝 代码&#xff1a; #include<stdio.h> #include<string.h> #include<stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(int argc, const char *…

SQL Server注入之攻防技战法

那天下着很大的雨&#xff0c;母亲从城里走回来的时候&#xff0c;浑身就是一个泥人&#xff0c;那一刻我就知道我没有别的选择了 1.Mssql报错注入 0.判断数据库类型 1.爆当前用户名 2.爆版本 3.爆服务器名 4.判断数据库个数 5.获取全部数据库 语句只适合>2005 爆当前数据…

桶式移位器

前言 本篇文章介绍CPU的核心部件之一&#xff1a;桶式移位器&#xff0c;简称BS&#xff0c;英文全称为Barrel Shifter 桶式移位器最大的特点就是能在单周期内完成多种方式&#xff0c;各种位数的移位操作 常见的移位操作 常见的移位操作种类如下&#xff1a; 算术右移 是指…

google-java-format 配置及应用

以google 的java 风格为基准&#xff0c;以后面的开发中&#xff0c;都需要满足这个&#xff0c;主要用到的是google的两个插件&#xff1a;google-java-format, google_checks.xml代码格式检测&#xff0c;这两个堪称双剑合并&#xff0c;代码成诗。google-java-format替换ide…

RS485数据采集模块,如何一次采集多个modbus设备数据?

在工业数据采集中&#xff0c;RS485是一种常见的数据通信协议&#xff0c;而Modbus则是其上的常用设备协议。那么&#xff0c;如何用一个模块高效采集多个Modbus设备的数据呢&#xff1f;这就是我们今天要探讨的话题&#xff01; 什么是RS485数据采集模块&#xff1f; 首先&a…

算法28:力扣64题,最小路径和------------样本模型

题目&#xff1a; 给定一个二维数组matrix&#xff0c;一个人必须从左上角出发&#xff0c;最后到达右下角 。沿途只可以向下或者向右走&#xff0c;沿途的数字都累加就是距离累加和 * 返回累加和最小值 思路&#xff1a; 1. 既然是给定二维数组matrix&#xff0c;那么二维数…

FreeRTOS——队列及其实战

1.队列概念 1&#xff09;队列是任务到任务、任务到中断、中断到任务数据交流的一种机制&#xff08;消息传递&#xff09; 2&#xff09;队列类似数组&#xff0c;只能存储有限数量、相同类型的数据&#xff0c;在创建时需指定队列长度与队列项大小 3&#xff09;出队入队阻塞…

xshell登录不上虚拟机了

电脑重启后连不上本地虚机了 1、关闭防火墙 2 虚拟机ping得到主机&#xff0c;而主机ping不到虚拟机的解决办法 原因&#xff1a;可能是主机的网络适配器没有调好 首先&#xff0c;找到虚拟机的网络配置器 根据虚拟机的IP信息修改主机虚拟适配器VMnet8 修改ip使得和虚拟机连…

Element-ui自定义input框非空校验

1、vue自定义非空指令&#xff1a; main.js中自定义非空指令 当input框或下拉框中数据更新时&#xff0c;触发校验 Vue.directive(isEmpty,{update:function(el,binding,vnode){if(vnode.componentInstance.value""){el.classList.add("is-required");}e…

[Unity]实时阴影技术方案总结

一&#xff0c;Planar Shadow 原理就是将模型压扁之后绘制在需要接受阴影的物体上&#xff0c;这种方式十分高效&#xff0c;消耗很低。具体实现过程参考Unity Shader - Planar Shadow - 平面阴影。具按照自己的理解&#xff0c;其实就是根据光照方向计算片元在接受阴影的平面…

详解卡尔曼滤波(Kalman Filter)

1. 从维纳滤波到卡尔曼滤波 黑盒&#xff08;Black Box&#xff09;思想最早由维纳&#xff08;Wiener&#xff09;在1939年提出&#xff0c;即假定我们对从数据到估计中间的映射过程一无所知&#xff0c;仅仅用线性估计&#xff08;我们知道在高斯背景下&#xff0c;线性估计…

计算机创新协会冬令营——暴力枚举题目01

首先是欢迎大家参加此次的冬令营&#xff0c;我们协会欢迎所有志同道合的同学们。话不多说&#xff0c;先来看看今天的题目吧。 题目 力扣题号&#xff1a;2351. 第一个出现两次的字母 注&#xff1a;下述题目和示例均来自力扣 题目 给你一个由小写英文字母组成的字符串 s &…

RocketMQ5.0Pop消费模式

前言 RocketMQ 5.0 消费者引入了一种新的消费模式&#xff1a;Pop 消费模式&#xff0c;目的是解决 Push 消费模式的一些痛点。 RocketMQ 4.x 之前&#xff0c;消费模式分为两种&#xff1a; Pull&#xff1a;拉模式&#xff0c;消费者自行拉取消息、上报消费结果Push&#x…

探索Allure Report:提升自动化测试效率的秘密武器

亲爱的小伙伴们&#xff0c;由于微信公众号改版&#xff0c;打乱了发布时间&#xff0c;为了保证大家可以及时收到文章的推送&#xff0c;可以点击上方蓝字关注测试工程师成长之路&#xff0c;并设为星标就可以第一时间收到推送哦&#xff01; 一.使用 Allure2 运行方式-Python…