【Linux基础】进程(上) —— 概念、状态、优先级与环境变量
目录
一、进程的概念
1. 什么是进程
PCB进程控制块的理解
2. 查看进程的方式
ps ajx 指令
getpid系统调用
3. 另外一种查看进程的方式(了解)
4. 进程的常见调用
fork 创建子进程
现象说明
二、进程的状态
1. 操作系统层面的进程状态
① 运行状态
② 阻塞状态
③ 挂起状态
2. Linux操作系统层面的进程状态(具体操作系统)
① R(running) 运行状态
② S(sleeping) 休眠状态(阻塞状态的一种):浅度睡眠
③ T(stopped) 暂停状态(也算一种阻塞的一种,阻塞是针对用户来看的代码不跑了,卡住了)
④ D(disk sleep)状态:深度睡眠状态
⑤ t 状态 (tracing stop) 暂停状态:表示该进程正在被追踪
⑥ Z(zombie) 僵尸进程的僵尸状态
⑦ X (dead) 死亡状态
3. 僵尸进程与孤儿进程
① 僵尸进程
产生过程
特征
危害
② 孤儿进程
三、进程的优先级
什么是优先级
权限
优先级
Linux 中是由两个整数 PRI 和 NI 确定优先级
ps -al 查看进程优先级
调整进程优先级
四、进程的其它概念
竞争性
独立性
并行
并发
进程切换
五、环境变量
1. 基本概念
程序运行的前提条件
操作系统查找程序的方式
环境变量的由来及作用
2. 常见环境变量
查看环境变量方法
① PATH 环境变量
查看PATH环境变量
第二种让自己程序不带路径运行的办法
PATH环境变量的理解
再次理解环境变量的概念
查看环境变量的 env 指令
② USER 环境变量
环境变量的意义
USER 环境变量解释 ” Permission denied ”产生的原因
3. 和环境变量相关的命令
命令行上也能定义变量
环境变量的设置
本地变量
export 指令
set 指令
PWD 环境变量
PWD 环境变量的意义
4. 对选项的理解
main 函数中还可以传一个参数 char **envp
environ 指令
对环境变量的三种获取方式
5. 对环境变量的总结
一、进程的概念
1. 什么是进程
我们经常看到这样的说法:
①一个运行起来(加载到内存中)的程序就是进程。
②在内存中的程序。①②这个两个东西说的是一回事,但是我们很难理解,什么是进程。
③进程和程序相比,进程具有动态属性。我们更不知道这是在说什么了。
以上这些说法都没错,但是不好理解。
操作系统的管理思路:先描述、再组织
当程序编译好后会生成二进制可执行文件存放在磁盘上,按照冯诺依曼的理论,要让程序运行起来就得先将其加载到内存中。当有很多程序都被加载进内存后,操作系统肯定要对它们进行管理。管理的方式是先描述这些程序,比如记录它们的相关信息,再对它们进行组织。而且对于先执行哪个程序、先调度哪个程序,操作系统也是需要进行管理的。
首先得把所有的进程先描述起来,先描述每一个进程,计算机内部为了先描述进程,有了一个PCB的概念(Linux内核中用结构体去描述)
PCB进程控制块的理解
进程控制块(PCB)本质上是一个数据结构,简单理解就相当于一个结构体,用于存储进程的相关信息。在 Linux 内核中,这个数据结构被命名为
task_struct
。这是因为 Linux 内核开发者选择了这个名称来代表进程控制块在 Linux 系统中的具体实现, 操作系统上叫 PCB。
先描述
再组织
- 观察上图 "再组织":
- 对于 CPU 而言,在操作系统调度进程时,例如要获取优先级最高的进程(假设存在进程 A、B、C 且优先级依次降低),其操作如下:操作系统会遍历进程的 PCB 链表,依据 PCB 中记录的进程优先级信息,找到优先级最高的 PCB,进而获取该进程的代码,然后将其交付给 CPU,CPU 便开始执行相应代码。
- 当某个进程退出时,操作系统会直接遍历 PCB 所在的链表,查看 PCB 状态是否处于 “死亡” 状态,一旦找到对应的节点,就会释放 PCB 所对应的代码和数据,最后释放 PCB 本身,至此该进程被完全释放。
- 实际上,所谓的进程管理,本质上是对进程对应的 PCB 进行相关操作,这进一步转化为对链表的增删查操作。当新增一个进程并加载到内存后,操作系统会创建与之对应的 PCB,填写好相关属性后,将该 PCB 链入链表;而在进程结束时,通过遍历链表找到处于特定状态(如 “死亡”)的 PCB 节点,完成资源释放,从而实现对进程的有效管理。
综上所述:进程 = 内核数据结构(task_struct) + 进程对应的磁盘代码
- 注意:进程 = PCB(如 Linux 中的
task_struct
)+内存中对应的代码和数据,当进程被操作系统调度执行时,CPU 是和内存中的代码和数据进行交互来完成计算、数据处理等任务的。 - 进程 = 内核数据结构(
task_struct
)+ 进程对应的磁盘代码,它主要强调了进程代码的来源。在进程启动之前,代码是以二进制可执行文件的形式存储在磁盘上的。当进程开始启动时,操作系统会从磁盘读取这个文件并将其加载到内存中,这个过程体现了进程和磁盘代码之间的关联。 - 所以这两种说法在不同的情境下都可以帮助理解进程的不同方面。
- 对进程概念的理解,前面说的概念没有错,只有动态属性没有体现出来,接下来我们就来见见动态属性。
-
2. 查看进程的方式
-
ps ajx 指令
ps ajx 显示我们系统中所有的进程
ps ajx | head -1 && ps ajx | grep 'myproc'
ps ajx | head -1:显示系统中的所有的进程,拿到第一行标题
ps ajx | grep 'myproc':查看该进程
我们会看到在进程显示中有grep,grep也是一个指令也会跑起来自己也在跑也是一个进程,所以就显示了。
以下说明进程在调度运行的时候,具有动态属性
当该程序跑起来了,运行起来了怎么查看该进程呢???
杀掉一个进程 kill -9 进程id (即pid)
getpid系统调用
getpid 是一个系统调用,用于获取当前进程的进程标识符(PID)。
进入手册 man getpid,里面有头文件等一系列说明信息
3. 另外一种查看进程的方式(了解)
每次运行pid都是不一样的正常吗???
一个进程每次被重新启动,每次重新加载到内存,每一次加载到内存意味着操作系统要给这个进程重新创建 task_struct, 重新分配 pid。
1. 以数字开头的目录对应的就是进程的pid,因为一个进程也可以被当做文件来看待
查看该目录带上-d 选项
接下来把进程退出掉,这个目录就会自动被系统回收,Ctrl+C
组合键通常可以终止当前正在运行的前台进程,按下 Ctrl+Z
会将当前前台进程暂停
2. 进程在磁盘上对应的可执行程序的路径
再复制一个会话,把可执行文件删掉进程还在跑 ,我们把这个进程杀掉,在myproc下就不会存在这个进程了
4. 进程的常见调用
主要看现象,我们需要学习到进程地址空间和进程控制才能解释这种现象。
getppid: 获取父进程pid,我们会发现父进程 pid 没有变化,我们发现7886是bash
- 当你在终端(由 bash 等 shell 提供)通过命令行输入命令来启动一个普通程序时,这个程序的父进程通常是 bash。
- 这是因为 bash(或者其他 shell)负责解释并执行你输入的命令。当你输入一个程序的启动命令(例如 ./myproc)后,bash 会使用系统调用(如 fork)创建一个新的进程,这个新进程就是你要运行的程序对应的进程,而这个新进程的父进程就是 bash。
注意:命令行上启动的进程,一般它的父进程没有特殊情况的话,都是bash,shell 运行是以子进程的方式进行执行的。
fork 创建子进程
问题:1. shell创建子进程,2. 把我的程序跑起来了
我们进入手册 man fork
创建子进程: fork是一个函数
函数执行前:只有一个父进程
函数执行后:父进程 + 子进程
继续观察 man fork 手册,fork函数存在两个返回值
现象说明
同一个变量id,在后续不会被修改的情况下,竟然有不同的内容,这个现象我们现在解释不了,进程地址空间内容,这就是系统和语言的区别。
对于fork,既然我们理解不了它,就不理解,就接受它,运用它,既然返回了不同的值,我们用 if 来运用这个返回两个值的现象,重新编写一下代码。
观察下面代码和结果我们会发现一个现象:
经过这些就更奇怪了,if 和else if 能同时成吗???今天就能了,不是我们学错了,而是我们学了fork,两个死循环可以同时执行吗,今天两个死循环既然同时可以执行。
fork之后后续的代码会被父子进程执行。
fork()之后,会有父进程 +子进程两个进程在执行后续代码
fork后续的代码,被父子进程共享!
通过返回值不同,让父子进程执行后续共享的代码的一部分。
相当于我把一个任务分成两份,父子进程分别在做,这就是并发式的编程
父进程和子进程同时在运行,这就是多进程
二、进程的状态
我们在学习进程的状态有哪些???
运行、新建、就绪、挂起、阻塞、等待、停止、挂机、死亡 ….等待
阻塞是代码不跑了吗?
压根不知道你在说什么,什么是新建,新建和运行又有什么区别呢?
以上都是操作系统的说法。
在不同的操作系统中,这个状态也会存在差别,我们只需要先知道运行、阻塞、挂起状态
这些状态不够具体,接下来我们要学习Linux中的具体状态
为什么会有这些状态???
进程这么多状态,本质都是未来满足不同的运行场景的!
1. 操作系统层面的进程状态
一、操作系统对外设管理
- 遵循 “先描述、再组织” 原则,通过获取硬件的状态信息来知晓硬件情况。在硬件的属性信息里能够定位到驱动所对应的操作方式,进而实现对硬件的操控。
二、磁盘与进程加载
- 磁盘作为存储介质,存有如 “sort.exe” 这样的可执行文件,其中包含排序算法等程序逻辑。
- 当系统启动或运行相关程序需求时,文件从磁盘被加载至内存。在此过程中,涉及进程管理,以 Linux 系统为例,进程控制块 “task_struct” 发挥关键作用,它涵盖进程的各类属性,进程自身的代码与数据也随之加载入内存,为后续运行做准备。
三、CPU 与进程运行
- 根据冯诺依曼体系结构,操作系统开机后被加载到内存,相关的数据结构如 “task_struct” 以及各类属性结构同样存于内存。这意味着程序要运行,必须先加载至内存,而操作系统为有效管控进程,需对其 “先描述、再组织”。
- 当考虑单 CPU 情况(暂不涉及多核复杂场景),由于 CPU 资源唯一,多个进程竞争运行机会,在内核中 CPU 需维护一个运行队列 “struct runqueue”,用于管理进程的运行次序。本质上,进程若要在 CPU 上运行,就是让其对应的 “task_struct”(即 PCB)按照先进先出等规则进入运行队列。
- 运行队列具有一些关键属性,例如队列中的进程个数,它反映了当前等待运行的进程规模,便于 CPU 调度时了解整体情况。CPU 调度时,依据运行队列的头指针找到某个进程的 PCB,凭借 PCB 中记录的信息拿到对应的代码,最终执行代码,完成进程的运行流程。
① 运行状态
CPU 虽只会按规则执行指令显得 “笨”,但速度极快,能迅速轮转运行队列里的进程。运行队列中的进程都处于运行状态(凡是运行队列里面的所有进程都叫做运行状态),需随时待命,以便被随时调度运行。
注意:进程的 “运行状态” 指其处于运行队列中。一是正在 CPU 上执行代码的进程是运行状态;二是虽没在 CPU 上跑,但在运行队列里的进程同样为运行状态。只要进程的 PCB 在 runqueue 里,标记为 “R”,就是运行状态,并非只有正在运行才叫运行状态。
在操作系统中,进程的所有属性都存放在 PCB 里,其中包括进程状态,它通常用一个整数表示,不同整数对应不同状态,如 0 可能表示就绪、1 表示运行、2 表示阻塞等,通过该整数即可知晓进程所处状态。
操作系统的运行状态以下这个例子我们好理解:
比如未来我们投简历,不是把我们这个人扔到邮箱里,而是把简历扔到邮箱里,公司有简历池,到简历池中挑选,对应的某个人,简历上有你的电话号码邮箱,简历有方法可以找到你这个人,所以公司内部对你们应聘者排队,对简历进行排队,通过简历找到你,公司的简历池,相对于运行队列,并且会对这些简历进行排队整理,方便后续挑选查看。HR在挑简历的时候,就是在执行调度算法,对简历进行排队,这个简历就相当于PCB,简历有没有你的电话,有没有你四级过了,你的技能,也就是你自己的描述结构体,你投简历不是把你这个人投过去,是把你的简历投过去,CPU就相当于面试官,HR就是操作系统的调度算法,你就是一个个进程,你的简历就是 PCB 在操作系统内部挑出来了一个进程,把这个进程的 PCB信息加载到 CPU 内存里面,同时 CPU 根据 PCB 找到你对应的代码和数据去执行,执行的过程就是面试你的过程。
② 阻塞状态
概念:在磁盘等待队列中等待某种资源的进程的状态,我们称为阻塞状态。
根据冯诺依曼体系结构,CPU 运算速度极快,相比之下,外设的速度则慢得多。在实际运行中,进程或多或少都需要访问外设,不过有的只是少量访问。像调用 printf 往显示器打印内容、通过特定方式向网络中写数据(借助硬件网卡)、用 scanf 或 cin 从键盘读数据、编写图形化程序访问显卡,以及进行文件操作实现对文件的 I/O 等,都是进程访问外设的常见场景。
以迅雷下载、百度网盘使用、在 B 站观看视频为例,当这些应用对应的进程被运行起来后,都会涉及到对外设的访问,像网卡就可能被多个进程同时需要。由于外设速度慢,且每种硬件数量往往有限,当多个进程都要访问同一外设时就容易出现问题。
比如拿磁盘来说,若进程 A 正在访问磁盘,此时进程 B 和进程 C 也需要访问,因为磁盘同一时间通常只能处理一个访问请求,所以 B 和 C 就只能等待。这表明进程不只是会占用 CPU 资源等待运行,还随时可能因为要访问外设资源而陷入等待。
面对这种情况,操作系统需要对硬件进行管理,采取的方式是先描述、再组织。对于每种硬件,操作系统都会构建相应的结构体,里面详细记录硬件的各种属性、状态等信息,并且在这些结构体中还设有自己的等待队列。当有多个进程争抢访问某一硬件时,操作系统就依据等待队列,按照一定的调度算法,有序地安排进程对硬件的访问,以此确保硬件资源的合理利用和系统的稳定运行。
观察下图:
当一个cpu正在执行某一个进程,比如该进程正在执行,突然发现这个进程要向磁盘写入,可是发现磁盘很忙,可是cpu不想等磁盘,cpu在执行你当前进程的代码,如果等你从磁盘写完太拖拉cpu速度,cpu的解决措施:看到该进程要访问磁盘了,就把该进程从运行队列中拿出来链入到磁盘的等待队列中,如果磁盘好了,操作系统会知道的。
当磁盘等待队列的进程好了,操作系统知道了,你这个进程可以运行了,是直接运行吗??
不是,把该状态改成R状态,然后把该PCB程序链入运行队列中,然后cpu会自动的调度这个进程,继续完成执行 。
对于cpu来讲,永远执行的是运行的进程,一旦你的代码有需要访问外设的进程,需要cpu等的时候,就把该进程放到对应硬件的等待队列中,把该进程剥离下来的同时,把该进行设置为阻塞状态,意味着你这个进程当前不能被直接调度,而是当前在等待某种资源,一会把你放到运行队列一会放到等待队列中,都是对你PCB对象放到不同的队列中。
操作系统的阻塞状态以下这个例子我们好理解:
想象一下银行营业厅的场景,这与 CPU 调度进程有着相似之处。银行里有多个柜台工作人员,就如同计算机里的 CPU,他们负责处理各种客户业务,而客户们就像是一个个进程。
假设一位客户来到银行办理一项复杂业务,需要填写一张详细的表格,耗时大概 10 分钟。银行柜员不会干等着这位客户填完,就像 CPU 不会在一个进程等待外设资源(如磁盘读写)时闲置一样。此时,柜员会招呼下一位已经准备好的客户,也就是 CPU 会继续调用其他就绪的进程。
比如,另一位客户只是来简单查询账户余额,这个业务办理起来可能只需 1 分钟,柜员迅速处理完这位客户的需求后,又有一位客户拿着提前填好的取款单来取款,柜员接着处理。这期间,第一位填表格的客户还在专心填表,他所处的状态就如同进程在磁盘等待队列中等待资源的阻塞状态,虽然暂时没有被服务,但业务并未中断,银行整体的服务流程还在高效运转。
当第一位客户终于填完表格,他就像阻塞状态的进程等到了磁盘资源准备就绪,会重新进入柜员的 “就绪队列”,等待柜员再次为他办理后续复杂业务,而在此期间,CPU(柜员)一直在高效处理其他能即刻开展的业务,保证了整个系统(银行营业厅)的运作效率最大化。
运行和阻塞状态小结:
- 一个cpu一个运行队列
- 让进程如队列。本质:将该进程的task_struct结构体对象放入运行队列中
- 进程PCB在runqueue,就是R,不是这个进程正在运行才是运行状态
- 不要只意味着你的进程只会等待(占用)CPU资源,你的进程也可能随时随地要访问外设资源
- 所谓的进程不同的状态,本质是进程在不同的队列中,等待某种资源
运行和阻塞很好理解,新建和就绪也很好理解就是这个进程刚刚创建好,新建和就绪没有什么用。
③ 挂起状态
一个进程暂时把它的代码和数据换置到磁盘的这种进程,我们叫做该进程被挂起了。
在操作系统中,存在一种称为阻塞状态的进程状态。当进程正在等待某种硬件资源(例如磁盘)时,便会进入阻塞状态。一旦进程进入阻塞状态,它就暂时不会被 CPU 调度执行,即便所等待的资源已然就绪,进程也不会立即恢复运行,而是要先由阻塞状态转变为就绪状态(通常用 “R” 表示),接着由操作系统将该进程的 PCB 放入运行队列中,后续等待 CPU 调度,迟早会轮到它再次获得运行机会。
倘若存在三个进程同时都处于阻塞状态,即便它们所等待的资源都已准备好,这三个进程同样不会立即运行,而是要先一步一步按照上述流程,被放置到运行队列里排队等候。
值得注意的是,在进程处于阻塞状态期间,其 PCB、程序代码以及相关数据都会占用内存空间。然而,系统里往往运行着诸多进程,并且大部分进程由于需要频繁访问外设,进而频繁地陷入自身阻塞状态,导致 CPU 无法立即对它们进行调度,必须得等待特定资源就绪才行。
从资源利用角度来看,对于那些处于阻塞状态,短期内 CPU 不会调度,意味着其在内存中的代码和数据暂时不会被执行的进程,如果内存空间不够用了,这无疑是个棘手问题。毕竟,这些短期内不被使用的进程却还占着内存空间,造成资源浪费。
为应对这一困境,操作系统采取了一项巧妙举措:既然进程短期内不需要使用内存中的代码和数据,那就把这些代码和数据暂时保存到磁盘上专门预留的一块空间中,如此一来,便腾出了一部分内存空间。
等到这个进程再次就绪,也就是所等待的资源再次满足要求时,操作系统会重新把该进程的代码和数据加载到内存里,将其状态改成R状态,再按照既定的调度规则对其进行调度,使其有机会重新运行,从而确保系统整体的高效稳定运行。
阻塞不一定挂起,挂起一定阻塞
2. Linux操作系统层面的进程状态(具体操作系统)
我们观察Linux内核源码中的进程的状态,存在以下这些状态。
这些状态没有新建就绪挂起阻塞,不代表它没有这些状态。
① R(running) 运行状态
② S(sleeping) 休眠状态(阻塞状态的一种):浅度睡眠
当我们在程序里使用
printf
函数的时候呀,它得去访问外设呢,这里说的外设就是显示器啦。要知道呀,显示器的速度那可比 CPU 慢太多太多了。CPU 那可是相当快的呀,眨眼间就能处理好多指令呢。可一旦要和显示器打交道,要把
printf
想输出的数据送到显示器上,CPU 就得等着显示器准备好,等着它能接收数据并且把内容显示出来呀。这个等待的时间,要是跟 CPU 平时处理指令那速度一对比,可就显得特别长啦,毕竟 CPU 正常运转起来那效率高着呢,这会儿却只能干等着,可不就相当于花费挺长的时间了嘛。而且呀,在这个过程中呢,进程就会进入一种浅度睡眠状态哦,这种状态下的进程是可以被终止的呢,只要按下快捷键
Ctrl + C
就行啦。这里面呀,其实这就是一种阻塞状态啦,为啥这么说呢?就是因为它要去访问外设,这个时间是相对于 CPU 来讲的,访问外设确实需要花费不少时间,所以进程就只能等着,处于阻塞的状态了。不过呢,咱们人用肉眼去看呀,感觉好像
printf
一执行,内容一下子就显示在屏幕上了,可千万不能按照咱们自己感觉的时间来衡量,在计算机内部,CPU 等外设这过程对它来说确实是等了挺长时间的,只是咱们察觉不出来罢了。
③ T(stopped) 暂停状态(也算一种阻塞的一种,阻塞是针对用户来看的代码不跑了,卡住了)
kill -l
指令用于列出系统中所有可用的信号名称和对应的信号编号。kill -19 pid 暂停
kill -18 pid 继续
前台进程:命令行输入指令不能解析。也就是带+号。
后台进程:命令行输入指令可以解析。不带+号。
④ D(disk sleep)状态:深度睡眠状态
在 Linux 系统中,存在一种被称为 D 状态,即深度睡眠(disk sleep)状态。
以进程 A 为例,当进程 A 需要将一万条数据写入磁盘的特定位置时,它会把数据传递给磁盘。由于磁盘属于外设,其读写速度相较于计算机内部的 CPU 等组件极其缓慢,写操作需要耗费一定时间。于是,在磁盘忙于写入这些数据的过程中,进程 A 便进入等待磁盘 I/O 完成的阶段,此时进程 A 处于阻塞状态。
假设在此期间,计算机内部的资源状况急剧恶化,出现内存严重不足的情况。操作系统通常会采取一些措施来应对,比如尝试将部分进程挂起,以释放内存空间。然而,若挂起操作仍无法有效缓解内存压力,问题依旧严峻,在这种极端情况下,Linux 操作系统可能会选择自主杀掉一些进程,回收它们所占用的资源。
但对于处于 D 状态的进程 A 而言,它具有特殊性。进程 A 被设置成在深度睡眠期间不会被操作系统轻易杀掉,这是因为如果杀掉进程 A,可能会引发一些不良后果。比如当磁盘好不容易完成数据写入,却发现进程 A 已被操作系统终止,此时磁盘写入的数据将失去意义,只能无奈丢弃,造成数据浪费与系统资源的无效消耗。
所以,处于 D 状态的进程,无法被操作系统常规手段杀掉,只能通过断电这种极端方式,使得整个系统重启,或者等待进程自己从深度睡眠中苏醒,完成后续操作,以此来打破僵局,确保系统在复杂且资源紧张的环境下仍能相对稳定运行。
需要注意的是,虽然 D 状态能保护进程不被轻易终止,但大量进程处于 D 状态也可能暗示系统存在潜在问题,如磁盘 I/O 瓶颈、内存管理不善等,需要进一步排查优化。
⑤ t 状态 (tracing stop) 暂停状态:表示该进程正在被追踪
这就是为什么你可以调试,因为进程停下来了,等待你下一步执行,可以查看上下文。
⑥ Z(zombie) 僵尸进程的僵尸状态
僵(尸)死状态是一个特殊的状态,也是是意味着该进程变成了僵尸进程。
为什么会有僵尸状态???
在 Linux 系统里,进程创建是为完成特定任务,而追踪任务完成情况催生了僵尸状态。
进程创建后有两个关注角度:一是创建者想了解任务执行优劣,涵盖是否顺利、有无错误等;二是即便创建者不关心结果,Linux 系统基于稳定高效运行需求,必须掌握进程完成情况。
进程运行起来后,父进程因任务统筹、资源调配,操作系统从宏观资源管理、稳定性维护出发,都要监控进程执行结果。
关键在于进程完成任务后,父进程或操作系统需读取其退出状态,像正常结束与否、返回值等信息。所以进程结束瞬间不能全释放资源,要以僵尸状态保留关键信息,等被读取后才彻底退场。总之,僵尸状态对 Linux 系统进程稳定运行意义重大。
僵尸状态的类比生活中的例子,便于理解:
就好比一个程序员喜欢跑步,每天早上6-7点喜欢跑步,而又来了另外一个程序员,从我旁边经过,也在跑步,一看就是生手,由于不懂得配速,边跑还边掉头发,我心想他怎么跑这么快:突然离我100米的地方突然,吧唧一下就倒地上了,倒下了之后,我慢慢跑过去查看,这个人怎么倒下了,是不是困了,我仔细观察,在我直接里,这个人好像没有呼吸了,我近身一看,完了,这个人 还真的没有呼吸了,作为一个公德心的公民,首先想到的是打120,还得打110,不管怎么样,得告诉别人有一个人退出,120一看这个人不行了,110一看果然退出了,一旦警察判定这个人退出了,不是对这个人怎么样,而是先把警戒线拉起来,封锁现场,因为警察的给社会以及家属一个交代,至少得知道什么原因死亡的,也有法医进行检查、正常死亡还是他杀。当一个人已经死亡了,警察不是立即对这个人火花(回收)操作,而是先封锁现场,对这个人进程科学的医学鉴定确定这个人退出了,当警察确定完了,会不会说:好,我们收拾东西,我们撤吧,110/120车一开就走了,就剩下你和那个人在风中凌乱,更多的是你在凌乱。所以当警察检查到这个人就是正常死亡的,通知家属可以准备后面的事情了,这样我们就从一个检测的状态变成了最后一个可以火花(回收)的状态了
那么这里的警察就相等于父进程或者是操作系统,那个倒下去的人就相当于一个进程退出了,不会被立即回收,而是要等一等,让操作系统或者父进程来获取他的退出结果,退出之后然后再由他的Z状态变成X状态,供我们系统回收,所以这个人从倒下的那一刻起到被警察处理的时间段内所处的状态就是Z僵尸状态,僵尸状态是一个问题,僵尸状态它的存在是正常的,他已经退出,该进程对应的资源没有全部被释放,还有一部分资源一定要保存起来,这部分保存起来的资源需要供我们上层去读取它的状态,得知什么原因退出的。
查看僵尸进程
僵尸状态,进程退出,没有被(父进程/操作系统)回收。
创建子进程,让父进程不要退出,而且什么都不做,让子进程正常退出。
不断打印进程的状态观察变化:
while :; do ps ajx | head -1 && ps ajx | grep 'myproc' | grep -v grep; sleep 1; done
defunct 失效的,n死者
注意:
在 Linux 系统中,僵尸状态是进程执行完任务并退出后所处的状态。此时进程虽已 “死亡”,但操作系统未释放其资源,这是因为操作系统需保留进程退出信息供父进程获取。
假设父进程不回收子进程,子进程会一直处于僵尸状态,占用内存空间。僵尸进程虽代码和数据可释放,但进程控制块(PCB)仍保留。
僵尸进程会浪费系统资源,引发内存泄漏。父进程获取子进程退出状态后,僵尸进程进入回收状态(X 状态)。
僵尸状态无法轻易被杀死,需父进程回收以释放资源。
总结,僵尸状态是进程退出后等待父进程回收的状态,父进程不回收会导致资源浪费和内存泄漏。
⑦ X (dead) 死亡状态
Z状态之后才变成了X状态被回收了,所以我们看不到X状态。
- 我们发现没有新建,就绪,阻塞状态,但是这些状态中隐含了,理论和实践是有差别的。
3. 僵尸进程与孤儿进程
① 僵尸进程
子进程已经结束运行并释放了大部分资源,但进程控制块(PCB)仍未被释放,处于一种 “僵死” 状态的进程。
我们学习了僵尸状态,通常所说的僵尸状态主要是针对僵尸进程而言,但严格来说,僵尸状态是进程在结束后进入的一种特定状态,而处于这种状态的进程被称为僵尸进程。
产生过程
进程调用
fork()
创建子进程,子进程继承部分资源独立运行,结束后向父进程发送退出信号并保存退出状态至 PCB,随后进入僵尸状态等待父进程回收资源。特征
- 资源占用:子进程结束后 PCB 仍在内存,且在进程表中有表项,占用内存和进程表空间。
- 不执行任务:处于僵尸状态的进程不再执行代码和占用 CPU 时间,仅等待父进程回收资源。
危害
- 资源浪费:大量僵尸进程未及时回收会累积占用内存和进程表空间,导致系统资源减少,影响其他进程运行,甚至无法创建新进程。
- 系统异常:僵尸进程可能干扰系统进程管理和监控,使管理员难以了解实际运行情况,极端情况下可致系统不稳定甚至崩溃。
注意:僵尸进程是一个问题,我们在学习进程控制的时候进行解决这个问题。
② 孤儿进程
如果一个进程子进程先退出,父进程不退出,那么子进程就会进入僵尸。
如果父进程先退出,子进程还没退出,那么子进程就成了孤儿,万一该子进程退出了谁来管,谁来释放该子进程的资源。
本来资源的回收和内存泄漏的解决需要父进程来做的,你还比我子进程先走,那么谁来回收我子进程的资源呢???
while :; do ps ajx | head -1 && ps ajx | grep 'myproc' | grep -v grep; sleep 1; done 不断打印父子状态,方便我们观察。
杀掉子进程,子进程进入僵尸
重新跑起来,杀掉父进程
我们杀掉了父进程,怎么没有看到父进程的僵尸状态呢,原因是因为父进程也有父进程,命令行上的所有进程都是bash的子进程,所以bash把该父进程回收了。
但是我们发现子进程的 PPID 是1号进程,1 号进程就是我们所对应的操作系统
输入top,发现 PID = 1 就是对应的操作系统
ps ajx | head -1 && ps ajx | grep system 查看对应的操作系统
从以上可以得出结论:一个子进程和父进程在运行时,如果他老爹先走了,则这个子进程被操作系统,即1号进程所领养,那么这样的对应的子进程(被领养的进程)我们称为孤儿进程。
注意:被领养的进程就是孤儿进程,一旦一个前台的进程派生了子进程,一旦该进程被领养(变成孤儿),这个子进程就会切换变成后台进程,此时只能通过 kill -9 来杀掉该进程。
- 孤儿进程小结:
- 这种现象一定存在,子进程会被操作系统领养(即1号进程),为什么要领养?如果不领养那么子进程退出的时候,对应的僵尸就没有人能回收了!
- 被领养的进程叫做孤儿进程,如果时候前台进程创建的子进程,如果变成孤儿了,会自动变成后台进程,只能通过 kill -9 杀掉。
三、进程的优先级
什么是优先级
概念:先或者后获得某种资源的能力,就是优先级,先获得是优先级高,后获得就是优先级低。
权限
指主体是否被允许进行特定的操作或访问特定的资源,决定了 “能不能做”,如用户对文件有读、写、执行等不同权限,限制了其对文件的操作范围。
优先级
用于确定多个任务、主体等在执行顺序上的先后关系,解决 “先做后做” 的问题,像在多任务操作系统中,不同进程有不同优先级,以决定谁先获得 CPU 资源。
- 为什么会存在优先级
因为资源太少了,很多进程需要访问外设,资源太少,要访问它的人太多就会存在优先级的情况。
- Linux优先级
所谓优先级本质就是PCB里面的一个整数数字(也可能是几个), 比如我们在餐厅吃饭都会给一个取餐号,这就是在排队。
Linux 中是由两个整数 PRI 和 NI 确定优先级
进程优先级是我们要了解的内容。我们直接说结论。
pir = primary 优先, ni = nice 美好的,引申出‘’前面/有利‘’的意思。
最终优先级 = 老的优先级pri + nice值
ps -al 查看进程优先级
在Linux中,当一个进程被创建出来了,创建出来之后它的优先级必须被确定好,为什么??
因为调度器要调度这个进程。
在运行期间,我想调整进程的优先级,怎么办呢??
Linux支持进程运行中,进行优先级调整的,调整的策略就是更改nice完成!
一般情况下NI 值是 0。
调整进程优先级
我们调整优先级的时候,普通用户没有权限,所以使用sudo进行提权操作。
1. ps –al 查看优先级
2. 输入 top 然后输入 r 后,再输入你想调整的进程的PID。 在 top 中,q 可以退出。
3. 最后输入你要设置的值
4.再次查看优先级
把 NI 的值调整为 100
把 NI 的值调整为 -100
我们可以发现,NI 的取值范围就是[-20,19] 这个区间内,不会让你随意的调整优先级的。
把 NI 的值调整成 9
也就是说每一次设置的时候,老的优先级都是从80开始的,最终区间范围是:[80-20,80+19]。
四、进程的其它概念
竞争性
- 指在系统中,多个进程或资源之间存在对有限资源的竞争关系。当多个进程同时需要使用同一资源,如 CPU、内存、I/O 设备等,它们之间就会产生竞争,每个进程都试图获取资源以完成自身的任务。
- 示例:在一个多任务操作系统中,多个应用程序同时运行,它们都需要使用 CPU 进行计算。此时,CPU 资源是有限的,各个应用程序就会竞争 CPU 时间片,操作系统的调度器会根据一定的策略来分配 CPU 时间片,以协调这种竞争关系。
独立性
含义:进程的独立性是指每个进程在运行时都有自己独立的地址空间、程序计数器、寄存器等资源,并且与其他进程相互隔离,互不干扰。一个进程的运行不会直接影响到其他进程的运行状态和结果,除非通过特定的进程间通信机制进行交互。
示例:就比如抖音闪退了,会不会影响微信闪退,不会存在这样的问题,运行一个程序进程 跑起来,自己挂了不会影响别人的,运行期间互相不干扰,父子进程也不干扰。
并行
- 含义:指多个任务或进程在同一时刻同时执行,需要有多个处理器或多核处理器的支持,真正实现多个任务在物理时间上的同时进行。
- 示例:在一个拥有 4 核 CPU 的计算机系统中,同时运行 4 个不同的计算任务,每个任务被分配到一个独立的 CPU 核心上,这 4 个任务就可以在同一时刻并行执行,从而大大提高了系统的整体处理效率。
并发
并发,是现代计算机操作系统中一个重要的运行机制。当 CPU 开始执行进程代码时,并非要等这个进程在 CPU 上完整运行结束才切换,当代计算机普遍采用时间片轮转策略。
具体来说,不管一个进程执行完自身任务究竟需要多长时间,系统只会给它分配例如 10ms 的时间片。一旦时间到,这个进程就必须从 CPU 上剥离下来,进入等待状态,或者被放置到运行队列的尾部重新排队,接着操作系统会调用其他进程。
如此一来,即便只有一个单 CPU,在任何一个确切时刻确实仅有一个进程在运行,但通过这种不断切换进程的方式,就能让系统在一个时间段内,使得多个进程的代码都有机会得以推进。
打个比方,如果一个进程的时间片是 10ms,一秒钟内理论上就能让 100 个进程各运行一次。要是系统中只有五个进程,同样给每个进程分配 10ms 时间片,一秒钟内就会有 100 个调度周期,100÷5 = 20,这意味着在一秒之内,每个进程至少可以被调度 20 次。通过这种进程切换,让多个进程在一段时间内同时推进代码运行的现象,我们就称之为并发。需要注意的是,这里不能单纯以速度来衡量 CPU,重点在于多进程在单 CPU 下协同推进的这种模式。
进程切换
进程切换是实现并发的关键步骤,能让多个进程的代码在一段时间内看似同时推进,给我们宏观上多个进程都在执行的感受。那进程究竟是如何切换的呢?
首先,来看 CPU 的构成,我们画一个图示意,这里有一个 CPU,在其内部存在着大量的寄存器,要注意虽然寄存器数量众多,但对于整个 CPU 而言只有一套硬件。从宏观角度分,寄存器大致两类,一类是用户可见的,另一类是用户不可见的。
当系统要运行某个进程时,CPU 会把这个进程的 PCB(进程控制块)的地址加载进对应的某个寄存器里,如此一来,通过寄存器就能直接找到进程的 PCB,进而找到该进程的代码和数据。之后,CPU 永远都在重复做三件关键事情:第一是取指令,按照程序计数器(PC)所指位置从内存中取出指令;第二是分析指令,弄清楚指令的操作要求;第三是执行指令,完成指令所规定的操作。
而当进程的时间片用完,这个进程就需要从 CPU 上剥离下来。被剥离之后,在它运行期间产生的各种临时数据,也就是所谓的上下文数据要进行保存,这里的上下文数据指的是 CPU 内寄存器的数据。为什么要保存呢?因为当这个进程后续再次轮到运行机会,回来要做的第一件事不是立刻重新从头运行,而是先把曾经保存的上下文数据恢复上来,一旦恢复完成,此时 PC 指针就会指向该进程上次中断的那行代码,接着就能从曾经中断的地方继续往后运行。
所以,一定要记住,进程在切换的时候,必须要进行进程的上下文保护;而当进程恢复运行的时候,要进行上下文的恢复,这一系列操作合起来就叫做进程切换。
五、环境变量
1. 基本概念
程序运行的前提条件
程序若要运行起来,需先让操作系统找到其对应的二进制代码,无论是自己编写的程序还是系统指令,只有被操作系统找到后,才有可能被加载到内存中,因为 CPU 执行操作依赖内存中的数据和指令。
操作系统查找程序的方式
操作系统要找到程序,需要在特定路径下去找,不仅会找头文件、库,也会找可执行程序。但这并非随意就能做到,是基于一定前提的。
环境变量的由来及作用
操作系统在启动时,会从配置文件中读取软件安装的路径信息,并将这些信息导入内存,构建出名为环境变量的内存级变量。正是凭借环境变量,操作系统在后续需要查找可执行程序等相关内容时,能够依据其中记录的路径信息准确找到相应程序,若没有环境变量的指引,操作系统难以精准定位程序,程序也就无法顺利运行了。
总的来说,环境变量在程序运行的前期查找阶段起着关键的纽带作用,连接着操作系统与程序所在的实际路径,保障程序能被顺利发现并加载到内存进而得以运行。
2. 常见环境变量
查看环境变量方法
echo $NAME //NAME:你的环境变量名称
① PATH 环境变量
执行程序需先找到程序
无论是自己编写的程序(如文中的 “myproc”),还是像 “ls”“pwd” 这类系统自带的常用指令,它们都是可执行程序。而要执行一个程序,前提就是要先找到这个程序,找不到的话自然无法执行。在执行自己写的程序时,往往需要带上路径(如加上 “./” 表示当前路径)才能找到它并运行。
系统自带指令的便利情况
对于 “ls”“pwd” 等系统自带指令,执行时不需要带路径就能直接运行。这并不是说它们不需要被查找,实际上它们同样需要被找到,只不过系统已经帮我们默认进行了查找工作,让我们在使用这些指令时可以更便捷,无需手动指定路径。
让自己程序不带路径运行的办法
如果希望自己编写的程序在运行时也能像系统自带指令那样不需要带任何路径,可采取的办法是将自己的程序拷贝到系统安装指令的目录中(/usr/bin),之后再运行这个程序(如 “myproc”)时,就无需再额外指定路径了。
程序能否顺利运行关键在于能否被找到,不同类型程序在查找过程中有着不同的处理方式,有的靠手动指定路径,有的依靠系统默认查找,还可以通过特定操作让自己的程序享受类似系统指令的便捷运行方式。
注意:不建议这样做,你写的指令没有经过测试的不要安装到你Linux的系统里,拷贝的动作就是安装。
将自己的程序拷贝到系统安装指令的目录中
删除系统目录中的该文件
查看PATH环境变量
系统中存在名为 “PATH” 的环境变量,它在系统内是全局有效的。像 “ls”“pwd”“which” 等常用指令能够被系统找到并执行,正是依靠 “PATH” 环境变量。直接输入 “echo PATH” 无法查看其实际值,若想看 “PATH” 环境变量具体存储的内容,前面必须加 “$”符号,也就是输入“echo $PATH” 才行。
大家可以看到这里就是一串路径,冒号再跟上一串路径再冒号,再跟上一串路径,再带冒号,再跟上一串路径,再带冒号。
系统在执行指令时,会默认在每一个冒号作为间隔的每一条路径当中去检索、去搜索对应的指令。如果这个指令存在的话,那么它就找到并执行。搜索完所有的这些路径之后,要是发现指令不存在,那就直接报出 “指令 not found” 这样的报错了。
系统当中的指令能执行,是因为系统的指令在 “user bean” 这个路径下,所以就可以被系统找到。
第二种让自己程序不带路径运行的办法
添加到环境变量里面,如果这样写就会把系统的覆盖了,很多指令都用不了,但是这种环境变量属于内存级的环境变量,重新进入我们xshell就好了
我们不能覆盖系统的环境变量
export 导入环境变量,先把原来环境变量放进去,然后再导入要加进去的环境变量
export PATH=$PATH :文件名绝对路径
再取执行里面的文件不需要带路径了
PATH环境变量的理解
- 你的程序在运行时无法被直接执行,是因为其所在路径没在系统默认搜索路径 PATH 下。
- 系统中的指令能被找到,是因为有环境变量 PATH,PATH 里默认带有系统对应的路径搜索功能。which 指令在底层实现时,就是依据环境变量 PATH 来进行路径搜索,以查找要执行的指令。自己的路径若被添加到 PATH 里,用 which 不光能查系统指令,自己的指令也能查到。
- PATH 是环境变量,是操作系统在启动命令行解释器 shell 时,导入到 shell 上下文当中的。当执行指令时,得通过 PATH 指定的路径去搜索可执行程序。操作系统为帮我们找到可执行程序,定义了很多环境变量。
再次理解环境变量的概念
- 环境变量 PATH 只是操作系统要解决的众多问题里的其中一个方面。比如,Linux 操作系统还面临许多其他需要知晓的情况,像怎么知道当前登录用户是谁、当前自己的主机名是什么、正在使用的 shell 对应的种类是哪种,还有诸如当前采用的配色方案、所用的编码方式、记录历史命令的条数,以及 shell 运行期间各种动态库或者头文件对应的搜索路径等。
- 上述这些情况分属于不同的领域,各自有着不同的用途,有的是助力查找指令,有的用于确认登录用户身份,有的用来明确主机名,还有的是确定 shell 种类等。
- 鉴于不同种类的应用场景需求,操作系统在启动 bash(命令行解释器),进行命令行解释工作时,就必须预先设置好一批未来可能会用到的变量,而这批变量就是我们所说的环境变量。
查看环境变量的 env 指令
env 指令:列出当前所有的环境变量及其对应的值。
环境变量是操作系统为满足不同应用场景,预先在系统内设置的一大批全局变量。它们在整个系统中,从 bash 启动之后,一直都能被其他进程访问到,并且有着不同用途。
比如,通过 “echo $HOME”可以查看当前所处的默认家目录,而使用“echo $LOGNAME” 则能够知晓当前登录的用户名。
② USER 环境变量
函数的方式获取环境变量:getenv
环境变量的意义
环境变量的最大意义:可以标识当前的使用Linux用户是谁,我们知道当前是以谁的身份在运行我们的Linux的,这是这个环境变量的意义。
USER 环境变量解释 ” Permission denied ”产生的原因
以前执行指令、访问资源时,常遇到 “permission deny”,即权限被拒,为啥系统知道用户没权限呢?
当读取文件这类操作时,文件有对应的拥有者和所属组。系统指令在编写时,就内置了身份认证环节。运行程序代码、执行系统指令时,会先确认当前用户身份,同时能获取文件属性,以此判断用户对文件是否有相应权限。像之后有时间可以了解,用 stat 调用就能获取文件属性。
举个例子,以普通用户 “dyy” 身份想进入某老师工作目录,直接运行 “cd” 命令进不去,因为 “cd” 命令内部会检测。它依据环境变量 “user” 识别用户身份,确认你既不是该目录的拥有者,也不是所属组,作为 “other” 角色又没相关权限,所以拒绝访问。
总之,系统级指令大多都会做身份或权限认证,其中很重要的一环就是通过 “user” 这个环境变量来认证。
③ HOME 环境变量
HOME 环境变量的值通常是用户登录系统后默认进入的目录,也就是用户的主目录(家目录)。
3. 和环境变量相关的命令
命令行上也能定义变量
在 Linux 的命令行(也就是 bash)中,bash 具备定义变量的能力。例如可以直接输入 “a=100” 来定义变量,然后通过 “echo $a” 操作,就能显示出变量 “a” 的值,说明 bash 能在命令行直接定义相应的变量。
环境变量的设置
本地变量
export 指令
可以对环境变量做定义并且如果一个本地变量已经存在,你想把它导成环境变量。直接export 跟上变量名就可以。
set 指令
set 指令:显示本地定义的shell变量和环境变量。
unset:清除环境变量和本地变量。
PWD 环境变量
关于
ls
命令与PATH
环境变量
- 当执行
ls
这样的系统命令时,系统能找到它是因为PATH
环境变量的存在。PATH
中记录了一系列系统会去搜索可执行文件的路径,所以ls
所在的目录在PATH
包含的路径之中,系统就能顺利找到并执行ls
命令。查找文件的疑惑
- 然而,当执行
ls myproc
、ls myproc.c
或者ls Makefile
这类操作,也就是查找指定文件时,就涉及到系统如何知晓这些文件位置的问题。有人可能觉得文件就在当前路径下,但 “当前路径” 并非想当然就被知晓的,那系统(具体来说是ls
命令)怎么知道当前路径在哪里呢?
PWD
环境变量的作用
- 实际上,系统中有一个叫
PWD
(Print Working Directory,打印工作目录)的环境变量,它记录着当前所在的工作目录,也就是所谓的 “当前路径”。正是依靠PWD
环境变量,像ls
这样的命令在查找指定文件时,就能以该变量所代表的当前路径为基准,去判断文件是否在此处,如果没有找到,还可以结合相对路径或者绝对路径继续去别的位置查找文件。总之,PWD
环境变量为文件查找操作中确定起始查找位置提供了关键依据。
退回上级目录再次查看环境变量就会发生变化,环境变量是bash内维护的
PWD 环境变量的意义
在 bash 中,它会维护当前所处的路径,每当路径发生变化时,shell 会自动调整相应环境变量(比如 PWD 环境变量)的值。
当运行 “ls” 这样的指令时,实际是创建了一个子进程,因为 “ls” 本身也是个程序。而环境变量具有可被子进程继承的特性,“ls” 作为 bash 的子进程就能获取到这些环境变量,进而知晓自己当前所在的路径。
这就是为什么平时使用 “ls” 显示文件时不用带路径就能知道文件在哪里的原因。像刚学习指令时,常说 “ls”“pwd” 可用来显示当前路径,例如执行 “cd ~” 切换到根目录后再用 “ls”,显示的就是根目录内容,执行 “cd -” 退回来再用 “ls”,显示的又是对应路径下的内容。这都是因为在路径切换时,当前路径改变了,环境变量随之更新,“ls” 作为子进程继承了变化后的环境变量,明确了所在路径,所以只需按此路径找到文件并显示出来就行。
4. 对选项的理解
我们在学习 C 语言的时候,
main
函数中,命令行参数是指在程序运行时从命令行传递给程序的参数,主要有以下两个:
argc
- 它是一个整数,表示命令行参数的个数,包括程序名本身。例如,在命令行中输入
./myprogram arg1 arg2
,那么argc
的值为 3,因为有 3 个参数,分别是程序名./myprogram
、arg1
和arg2
。
argv
- 它是一个字符指针数组,用来存放命令行参数的字符串。其中
argv[0]
指向程序名,argv[1]
指向第一个实际参数,以此类推,最后以NULL结尾。例如,对于上述命令行输入,argv[0]
的内容是"./myprogram"
,argv[1]
的内容是"arg1"
,argv[2]
的内容是"arg2"
。
#include <stdio.h>
int main(int argc, char *argv[]) {
int i;
printf("命令行参数的个数为:%d\n", argc);
for (i = 0; i < argc; i++) {
printf("第 %d 个参数是:%s\n", i, argv[i]);
}
return 0;
}
在命令行解析中,argc
是一个整数,用于表示命令行输入的子字符串数量,包括程序名本身。argv
是一个指针数组,其中每个元素都是一个指针,分别指向命令行中的每一个子字符串,argv[0]
指向程序名,后续元素依次指向各个参数,通过它们程序可以获取和处理命令行传入的参数。
拿 “ls” 命令来说,执行 “ls” 默认是一种结果,用 “ls -a”“ls -l”“ls -al”“ls -a -d” 等不同带参数的形式,最终打印输出的内容各不相同,这都是通过命令行选项来控制的。
int main(int argc,char* argv[])
{
// ./myproc -a -b -c 单个选项的使用
// ./myproc -ab/-ac/-bc 选项组合
if(argc != 2)
{
printf("请使用:\n\t%s [-a/-b/-c/-ab/-ac/-bc]\n",argv[0]);
return 1;
}
if(strcmp("-a",argv[1]) == 0)
printf("功能a\n");
if(strcmp("-b",argv[1]) == 0)
printf("功能b\n");
if(strcmp("-c",argv[1]) == 0)
printf("功能c\n");
if(strcmp("-ab",argv[1]) == 0)
printf("功能ab\n");
if(strcmp("-ac",argv[1]) == 0)
printf("功能ac\n");
if(strcmp("-bc",argv[1]) ==0)
printf("功能bc\n");
return 0;
}
main 函数中还可以传一个参数 char **envp
当
main
函数带有相应参数(如int main(int argc, char *argv[], char *envp[])
这种形式)时,系统会为其传递两张表。其一为命令行参数表,也就是
argv
。它以指针数组的形式存在,里面的元素指针分别指向命令行输入的各个参数,像程序名以及后续用户输入的具体参数等,方便程序在运行过程中去获取并解析这些来自命令行的输入内容,进而依据不同参数来执行对应的操作。其二是环境变量表,即
envp
。环境变量在操作系统中承载着诸多如系统配置、用户相关设置等重要信息,像常见的PATH
(可执行文件搜索路径)、HOME
(用户主目录)等环境变量都在其中。当系统把envp
传递给main
函数后,进程就获取到了这些环境变量,也就能够在程序执行期间去使用它们了,例如根据PATH
来查找所需的外部可执行文件,或者依据HOME
来确定一些文件的默认存储位置等,让程序可以更好地适应所处的运行环境并按要求完成各项功能。
int main(int argc, char **argv, char **envp)
证明 argv 是以 NULL 结尾
环境变量被子进程继承,怎么被继承的呢???
我们可以导入 myval 后,它就出来了,因为这个环境变量是被导给你的shell的,而你运行你的程序的时候是需要创建子进程,并且把你shell的环境变量交给这个子进程的。怎么传呢?你可以理解成就是通过命令行参数传的,main函数我们在使用的时候一般不写这些参数。
environ 指令
man environ
对环境变量的三种获取方式
- getenv
- char *env[]
- extern char ** environ ,一般使用 getenv
5. 对环境变量的总结
- 程序启动时,系统要做不少事。先找到程序,再把它从磁盘加载进内存,期间配置好环境变量、设置好命令行参数,准备好后调用相关启动函数传参,程序才开始执行代码。按冯诺依曼体系,数据和代码得加载进内存,这是综合软硬件多方面因素决定的,像硬件上内存读写快利于 CPU 处理,软件里多任务及动态链接等功能也依赖内存加载。