当前位置: 首页 > news >正文

探索Linux进程:从理论到实践

文章目录

  • 前言
  • 冯诺依曼系统
  • 操作系统
    • 简介
    • 设计目的
    • 操作系统的管理
    • 系统调用
  • 进程
    • 进程概念
    • task_struct
    • 查看进程
    • 创建进程

前言

在Linux中,除了指令和工具,还有非常重要的部分——进程,这篇文章我们就简单介绍一下进程。

冯诺依曼系统

我们常见的计算机,大部分都遵守冯诺依曼体系结构,如下所示:
在这里插入图片描述
我们使用计算机,其实就是完成" 输入 -> 计算 -> 输出 "的过程,输入设备和输出设备统称为外设,外设是相对于中央处理器和存储器来谈的,存储器,指的就是内存,与之相对的,如磁盘等,称为外存
以下是存储分级的一张图表:
在这里插入图片描述
由于各设备之间的计算速度不同,输入设备和输出设备相对于CPU较慢,为了缓解外设与CPU之间的速度差异,在CPU与外设之间引入了内存,也就是冯诺依曼体系结构中的存储器。因此,在数据层面,CPU不和外设之间打交道,只会和内存打交道。
外设可以先将数据预加载到内存中,然后再加入到CPU中。此时,计算机的效率,主要是以内存为主。而冯诺依曼体系中,输入设备和输出设备是在内存角度来定义的。
计算机数据流动过程,本质就是拷贝,计算机效率的问题,其实是由设备的拷贝效率决定的。

操作系统

简介

任何一个计算机系统都包含一个基本的程序集合,称为操作系统(OS)。不同概念的操作系统包括的内容也不一样,如下图所示:
在这里插入图片描述
这里我们讨论的是狭义上的操作系统。
内核的四大功能包括内存管理、文件管理、驱动管理和进程管理,在任何计算机类的设备,都是需要有这四大功能的,只不过不同操作系统会在不同的功能上添加一些外壳程序(如图形化界面),来方便用户使用。

设计目的

操作系统对下要与硬件进行交互,管理所有的软硬件资源,对上要为用户提供一个良好的执行环境。因此,操作系统是一款进行软硬件资源管理的软件。如下所示:
在这里插入图片描述

操作系统的管理

操作系统的对软硬件资源的管理,简单来说,其实就是"先描述,再组织",这是操作系统最核心的工作,操作系统先将各种软硬件资源描述成一个结构体,再通过相关数据结构将这些结构体组织起来,管理相关软硬件资源,就转换成了对相关数据结构的管理。

系统调用

当用户访问想通过操作系统查看或修改数据时,不可能让用户直接对操作系统的内核相关数据做修改,这样可能会对操作系统的相关数据和结构进行破坏,但又为了让用户可以使用操作系统完成一些合法的操作,因此,操作系统提供了一些函数来供用户使用,这些函数叫做系统调用
但是,直接使用系统调用对用户的要求较高,所以可以将系统调用进行封装,以供用户来使用,封装之后就行了编程语言相关的库函数,库函数中很多都是对系统调用的封装。

进程

进程概念

程序和可执行文件其实是一回事儿,都是磁盘上的一个普通文件,指令的本质也是程序。
在操作系统上,可以同时运行多个程序,操作系统为了更好地管理进程,一方面不仅要将相关程序加载到内存,另外一方面还要对相关程序的相关属性进行管理。因此,每个内存中的程序都一定会有相关的结构体来描述内存中程序的属性,这也符合操作系统先描述再组织的管理规则,操作系统对内存中程序的管理,就可以转换为通过用数据结构对相关程序结构体的管理。
进程其实就是内核数据结构和对应的程序代码与数据。 如下图所示:
在这里插入图片描述
之前我们使用的"./…"指令运行程序、windows双击启动APP、执行Linux指令本质都是在启动进程。

task_struct

描述进程的结构体叫做PCB(进程控制块)。在Linux系统中,对应的PCB为task_struct。task_struct是PCB的一种,所有的进程,都可以直接或者间接通过这个结构体找到。相关属性的简介如下:

标识符:标识符是描述本进程的唯一标识符,用来区分不同的进程。Linux系统中为pid。
状态:用来表示进程的状态,这个后面会详细介绍。
优先级:表示该进程相对于其它进程的优先级,优先级可以使操作系统确定调度哪个进程。
程序计数器:程序中即将被执行的下一条指令的地址。
内存指针:包括程序代码和进程相关数据的指针,还有和其它进程共享的内存块指针。

除了上述属性外,task_struct还有一个非常重要的属性——上下文数据
在操作系统中,都会给每个进程分配一个时间片,时间片执行完毕,就会自动让出CPU,让其它进程执行,即便一个进程中的代码没有执行完,但由于时间片到达,就会进行进程切换和调度的动作,该进程就会将CPU让出去,而当该进程再次获得时间片时,由于之间该程序已经运行了一部分,产生了一些临时数据和变量,此时需要上下文数据来对数据进行保存。当该进程再次获得时间片被调度时,就需要根据上下文数据来恢复上下文数据。CPU中的寄存器只有一份,但上下文数据 可以有多份,分别对应不同的进程,因此,上下文数据指的是寄存器中的数据,是进程私有的。

查看进程

操作系统给我们提供了一个系统调用,以供进程获取pid,这个函数为getpid()函数。简单的示例程序如下:

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    int main()    
{    while(1)    {    pid_t id = getpid();    printf("我是一个进程,pid: %d\n",id);                                                            sleep(1);    }    return 0;    
} 

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

查看进程,可以使用"ps ajx"指令,如下所示:
在这里插入图片描述
多次运行程序,每次显示的pid结果都不一样。
每个进程除了pid之外,还有ppid,表示该进程的父进程的pid。如下所示:

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    int main()    
{    while(1)    {    printf("I am a process,pid: %d,ppid: %d\n",getpid(),getppid());                              sleep(1);                                                                                 }                                                                                           return 0;                                                                                   
} 

运行结果如下所示
在这里插入图片描述
在这里插入图片描述
Linux系统中,新的进程,往往是通过父进程的方式,创建出来的。
在命令行中,启动程序,程序的进程一直在变,但是父进程一直不变,我们可以查下父进程的相关信息,信息如下:
在这里插入图片描述
当我们启动自己的程序或执行指令时,都是将程序和指令交给-bash进程,-bash是命令行解释器,它会创建相应的子进程。
除此之外,查看进程,可以通过/proc目录查看,如下所示:
在这里插入图片描述
/proc目录下有很多用数字命名的目录,而每一个目录名都是对应进程的pid,也就是说,Linux可以将目录的信息实时以目录的形式展现出来。如下所示:
在这里插入图片描述
其中exe文件表示,在该进程启动前对应的二进制文件是哪一个。cwd文件进程对应的工作路径,每个进程启动的时候都默认有自己的工作路径,当不明确指定工作路径时,工作路径为当前路径,就如C语言用fopen()函数,参数仅仅填写一个不存在的文件名时,运行程序会在当前路径下创建文件。程序如下所示:

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    int main()    
{    FILE *fp = fopen("log.txt","w");    if(fp == NULL)    {    perror("fopen");    }    fclose(fp);    while(1)    {    printf("I am a process,pid:%d\n",getpid());    sleep(1);                                                                                      }    return 0;    
} 

运行结果如下:
在这里插入图片描述
log.txt创建在了程序所在的目录下,这是以为进程默认的工作路径就是当前进程的工作路径。若想要改变工作路径,可以使用chdir()函数。修改后的程序代码如下:

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    int main()    
{    chdir("/home/zhangsan");FILE *fp = fopen("log.txt","w");    if(fp == NULL)    {    perror("fopen");    }    fclose(fp);    while(1)    {    printf("I am a process,pid:%d\n",getpid());    sleep(1);                                                                                      }    return 0;    
} 

运行后查看log.txt:

查看对应进程的cwd文件:
在这里插入图片描述

创建进程

若想要创建新进程,可以使用fork()函数,fork函数是一个系统调用。使用示例如下:

#include <stdio.h>
#include <sys/types.h>                                                                         
#include <unistd.h>int main()
{printf("I am a process,pid:%d,ppid:%d\n",getpid(),getppid());fork();printf("I am a process(fork),pid:%d,ppid:%d\n",getpid(),getppid());
}

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

我们发现fork之前,只输出了一次,fork之后,输出了两次。说明fork之前,执行流只有一个,fork之后,执行流有两个,如下图所示:
在这里插入图片描述
fork函数会使执行流出现分流,这个就和fork()函数的返回值有关系。
在使用手册中,返回值是这样说明的:
在这里插入图片描述
简言之,fork()函数成功会有两个返回值。对自己程返回0,对父进程返回子进程的pid。如下所示:

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    int main()    
{    printf("I am a parent process,pid:%d\n",getpid());    pid_t id = fork();    if(id < 0)    {    return 1;    }    else if(id == 0)    {    //子进程    while(1)    {    printf("I am a child parent process,pid:%d,ppid:%d\n",getpid(),getppid());    sleep(1);    }    }    else    {    //父进程    while(1)    {    printf("I am a parent parent process,pid:%d,ppid:%d\n",getpid(),getppid());                                                                                                                         sleep(1);    }    }    return 0;    
}  

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

查看进程的信息:
在这里插入图片描述
通过返回值,我们就可以让父子进程按要求进行分流。
可如随之而来的有以下三个问题:

  1. 为什么给子进程返回0,给父进程返回的是子进程pid?
  2. fork()函数,一个函数为什么会返回两次?
  3. 一个id,怎么能接受两个不同的值?

对于问题一,每创建一个新进程,就会创建一个task_struct,子进程是以父进程为模板创建的,而父进程的代码和数据是从磁盘中加载到内存的,但是子进程默认是没有代码和数据的,所以,往往创建出来的子进程和父进程指向同一个代码和数据,fork之后,代码和数据,一般都是父子共享的。在实际中,一个父进程可以有多个子进程,将子进程pid返回给父进程,是为了方便父进程进行区分。但一个子进程只有一个父进程,不牵扯找不到父进程的问题,返回0只是为了表明子进程创建成功而已。
对于问题二,fork()函数在进行return返回的时候,fork()函数的核心功能已经完成了,而fork()函数的核心功能是创建一个子进程,那么也就是说,在fork()函数return的时候,子进程已经被创建了,此时已经有父子进程了。return本身也是代码,而默认情况下,代码和数据是父子共享的,那么父子进程各自都要执行return语句,也就有两个返回值。
对于第三个问题,这里和虚拟地址空间有关,我们后面会详细介绍。

http://www.dtcms.com/a/531853.html

相关文章:

  • 正则化机制解析:L2 的约束逻辑与 L1 的特征选择
  • 股票与期货战法理论发展路径
  • 用Python手写一个能识花的感知器模型——Iris分类实战详解
  • MySQL笔记16
  • gRPC通信流程学习
  • 百度站长平台有哪些功能网站做权重的好处
  • 数据科学复习题2025
  • 牛客网 AI题​(二)机器学习 + 深度学习
  • 拆解AI深度研究:从竞品分析到出海扩张,这是GTM的超级捷径
  • HarmonyOS 环境光传感器自适应:构建智能光线感知应用
  • 护肤品 网站建设策划shopex网站经常出错
  • 机器人描述文件xacro(urdf扩展)
  • AI决策平台怎么选?
  • 当 AI 视觉遇上现代 Web:DeepSeek-OCR 全栈应用深度剖析
  • 紫外工业相机入门介绍和工业检测核心场景
  • 商业求解器和开源求解器哪个更适合企业?
  • 比尤果网做的好的网站深圳网站设计精选刻
  • WPF 控件速查 PDF 笔记(可直接落地版)
  • Selenium+Unittest自动化测试框架
  • 设计模式-命令模式(Command)
  • 设计模式-外观模式(Facade)
  • web自动化测试-selenium_01_元素定位
  • 苏州建设工程信息网站wordpress自动生成tag
  • 学习C#调用OpenXml操作word文档的基本用法(1:读取样式定义)
  • Java-Spring入门指南(二十八)Android界面设计基础
  • Go 语言类型转换
  • 【Windows】goland-2024版安装包
  • 快速入门elasticsearch
  • Linux 多用户服务器限制单用户最大内存使用(systemd user.slice)
  • 食品公司网站设计项目雨蝶直播免费直播