欢迎来到Cefler的博客😁
🕌博客主页:那个传说中的man的主页
🏠个人专栏:题目解析
🌎推荐文章:题目大解析3
进程状态是指一个进程在其生命周期中可能处于的不同状态。常见的进程状态包括以下几种:
新建状态(New):当一个新的进程被创建时,它处于新建状态。此时操作系统已经为该进程分配了必要的资源,但还没有开始执行。
就绪状态(Ready):当一个进程已经准备好执行,但由于CPU资源有限,当前没有可用的CPU时间片来运行它时,该进程处于就绪状态。就绪状态的进程将等待分配到CPU资源。
运行状态(Running):当一个进程获得CPU时间片并正在执行时,它处于运行状态。在单核处理器情况下,一次只能有一个进程处于运行状态;而在多核处理器或多任务操作系统中,可以有多个进程同时处于运行状态。
阻塞状态(Blocked):当一个进程无法继续执行,因为它需要等待某些事件发生(如I/O操作完成、等待资源释放等),此时它将进入阻塞状态。在阻塞状态下,该进程会被移出CPU,并暂时停止执行,直到事件发生并且进程可以继续执行。
结束状态(Terminated):当一个进程完成它的任务或被操作系统终止时,进程进入结束状态。在结束状态下,进程的所有资源将被释放,并等待操作系统回收。
一个进程可以按照以上状态之间的转换进行切换。例如,当一个新建进程变为就绪状态时,它等待CPU分配资源;当分配到CPU资源后,进程从就绪状态变为运行状态;如果进程需要等待某些事件完成,它将从运行状态转为阻塞状态;一旦事件发生,进程从阻塞状态恢复并重新进入就绪状态等待CPU时间片,最终在任务完成时进入结束状态。
操作系统通过对进程状态的管理,可以合理调度和执行进程,实现多任务的并发执行。了解进程状态的转换可以帮助我们理解进程的行为和调度机制,以及进行进程调优和排查问题时的分析。
😾在代码的角度上
进程状态,就是描述结构体PCB中的一个字段,就是PCB中的一个变量,int status
所谓的状态变化,本质就是修改整型变量
😾在操作系统的角度上
只要在运行队列中的进程,状态都是运行状态。
每一个cpu都会在系统层面维护一个运行队列
我们的代码中,一定或多或少会访问系统中的某些资源
比如硬件资源:磁盘,键盘,网卡等……
比如访问键盘,但是用户始终不在键盘上进行输入,也就是进程要访问的资源未就绪 ,
所以进程代码就无法执行后续的代码
资源未就绪,操作系统要不要知道? 🤔
答:
必须的,因为操作系统要管理硬件,提到管理,就要先描述再组织。
OS是最先知道它所管理设备的状态变化
所以硬件资源也有属于它的"PCB",即描述结构体
struct dev
{
int type;
int status;
struct dev* next;
PCB* wait_queue;//若被阻塞,就放入这个队列中等待
//更多属性
}
可以表示为如下这张图👇🏻👇🏻
💥所以总结一下,进程状态变化的本质!!:
1.更改pcb中的status变量
2.将pcb连入不同的队列(运行/等待)
我们所说的所有的进程,只和pcb有关,与代码本身无关
所以当我们看到进程堵塞了,
现象是进程卡住了,但内核原因是pcb没有在运行队列中&&状态不是running,cpu不调度你的进程
挂起状态(Suspended)是进程可能处于的一种特殊状态,与其他常见的进程状态(新建、就绪、运行、阻塞、结束)不同。当一个进程被挂起时,它的执行会被临时中断,并且实际占用的系统资源会被释放,以便其他进程能够继续运行。
挂起状态通常分为两种类型:可恢复挂起和不可恢复挂起。
可恢复挂起(可中断挂起):当一个进程被可恢复挂起时,它的执行可以被恢复。这种状态下,进程被暂停执行,但其内部状态仍然保存在内存中,以便稍后重新开始执行。常见的可恢复挂起情况包括通过按下 Ctrl+Z 键将前台进程切换到后台、通过操作系统的挂起命令将进程挂起等。
不可恢复挂起(不可中断挂起):当一个进程被不可恢复挂起时,它的执行无法被恢复。这种状态下,进程的执行被完全中断,其内部状态不再保存。常见的不可恢复挂起情况包括进程被操作系统强制终止、进程因错误或异常而被终止等。
进程可以被挂起的原因包括但不限于以下情况:
当满足特定条件时,操作系统可以恢复可恢复挂起的进程的执行,并将其状态切换为就绪状态,以便进一步执行。在恢复执行之前,操作系统可能会将挂起的进程从磁盘交换到内存中。
总而言之,挂起状态是指进程执行被临时中断并释放系统资源的特殊状态。可恢复挂起的进程可以在稍后恢复执行,而不可恢复挂起的进程则无法继续执行。挂起状态的使用可以帮助操作系统合理分配资源和调度进程,以提高系统的效率和响应性。
在Linux操作系统下,进程状态可以分为以下几种:
运行状态(Running):表示进程正在运行或占用CPU执行指令。
就绪状态(Ready):表示进程已经准备好执行但尚未获得CPU资源。就绪状态的进程等待调度器将其分配给可用的CPU。
阻塞状态(Blocked):表示进程由于某种原因无法继续执行,需要等待某个事件发生或满足某个条件才能继续执行。常见的阻塞事件包括等待I/O操作完成、等待信号量、等待互斥锁等。
僵死状态(Zombie):当一个进程的执行已经结束,但其父进程尚未通过wait()系统调用来获取其终止状态时,该进程就会成为僵死进程,也称为僵尸进程。僵尸进程仍然在进程表中保留一些必要的信息,等待父进程处理,并且不再占用系统资源。
停止状态(Stopped):表示进程被暂时停止并且不会继续执行。进程可以通过信号(如SIGSTOP、SIGTSTP)或调试器(如gdb)中断来进入停止状态。与阻塞状态不同,停止状态下的进程不会被调度器重新唤醒。
前台状态(Foreground):表示与终端交互的进程当前在前台执行。通常情况下,用户可以通过终端输入来与前台进程进行交互,而其他非前台进程处于后台执行。
后台状态(Background):表示进程在后台执行,没有与终端进行交互。后台进程不会接收终端输入,但可以在后台继续执行。
进程状态之间的转换是动态的,并且由操作系统内核和调度器负责管理。例如,当一个进程变为就绪状态时,它等待调度器将其从就绪队列中选中并切换到运行状态;当进程遇到某个条件并需要等待时,它会从运行状态切换到阻塞状态等待事件发生。
了解Linux中的进程状态对于调试、性能优化和系统监控都非常重要,可以帮助我们理解进程的行为、调度机制以及进程间的交互。
在Linux中,可以通过ps
命令或者top
命令查看进程状态。以下是常见的Linux中用于标识进程状态的符号:
并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列
里
S:表示睡眠状态(Sleeping),即进程正在等待某个事件的发生。
D:表示不可中断的睡眠状态(Uninterruptible Sleep),进程正在等待一些无法中断的事件的发生。
T:表示停止状态(Stopped),即进程被暂停执行。
可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可
以通过发送 SIGCONT 信号让进程继续运行。
Z:表示僵死状态(Zombie),该进程的执行已经结束,但其父进程尚未处理终止状态。
<:表示进程在前台执行。
N:表示进程在后台执行。
X:死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
除了上述符号外,ps
命令和top
命令还可以提供更多的信息,如PID(进程ID)、USER(进程所有者)、%CPU(CPU占用率)、%MEM(内存占用率)等。可以使用命令的不同选项或参数来定制所需的输出。
需要注意的是,不同版本的Linux发行版可能会有轻微的差异,因此具体符号和标识可能会有所不同。在查看进程状态时,建议参考相关文档或手册以获取准确的信息。
当一个进程执行完毕后,它的退出状态和其他信息需要被保留,以供其父进程查询。这是为了确保父进程可以获取有关子进程执行结果的信息。在Linux系统中,当一个进程结束但其父进程尚未通过wait()
或waitpid()
函数来获取这些信息时,被称为僵尸进程(Zombie)。
僵尸进程并不具有实际的执行代码,它们只是在进程表中保留了一些元数据,如进程ID(PID)、退出状态等。这些信息可以通过系统调用wait()
或waitpid()
来获取,以告知父进程子进程的结束状态。当父进程成功获取这些信息后,操作系统会清理掉僵尸进程的数据,并释放相关资源。
僵尸进程产生的主要原因是父进程没有及时处理子进程的退出状态。这可能是由于父进程繁忙或者因为设计上的问题导致的。僵尸进程本身并不会对系统造成直接影响,因为它们不占用计算机资源。然而,如果系统中存在大量僵尸进程,可能会消耗掉进程表中的可用项,并最终导致系统资源不足。
总的来说,当一个进程在退出的时候, 退出信息由OS写入到当前退出进程的pcb,可以允许进程的代码和数据空间被释放,但是不能允许进程的pcb被立即释放,要让os或者父进程,读取退出进程的pcb的退出信息,得知子进程退出的原因,此时这个状态称为僵尸状态
为了避免僵尸进程的产生,父进程应该正确处理子进程的退出状态。通常的做法是父进程在创建子进程后,通过wait()
或waitpid()
函数等待子进程的退出,并及时处理其状态。这样可以确保子进程的资源得到释放,避免产生大量僵尸进程。
另外,操作系统也提供了一些机制来自动处理僵尸进程,如SIGCHLD信号和守护进程。通过正确使用这些机制,可以有效地预防和清理僵尸进程。
在 Linux 系统中,当一个父进程退出时,但它的子进程尚未退出或被其他进程接管时,子进程就会成为孤儿进程。孤儿进程将由系统进程 init
接管,init
进程会将其作为自己的子进程,并负责回收它们的资源。这样可以确保孤儿进程不会一直存在而占用系统资源。
当父进程退出时,内核会将子进程的父进程 ID(PPID)设置为 init
进程的进程 ID(PID),并且将操作系统的进程表更新以反映这种变化。这样,子进程就变成了一个孤儿进程。
孤儿进程具有与其他进程相同的进程状态(如运行、等待等),它们需要继续执行直到完成或被终止。一旦成为孤儿进程,init
进程将负责回收这些进程的资源,包括回收它们的内存、关闭打开的文件描述符等。
总结起来,孤儿进程是指父进程先于子进程退出,导致子进程成为无父进程的进程。Linux 中的 init
进程会接管这些孤儿进程,防止它们一直存在而浪费系统资源。
父进程退出,子进程一直进行,父进程状态?
答:父进程被bash回收,状态为S
Linux 的精灵进程(Daemon Process)是一种在后台运行的特殊类型的进程,它们与用户没有交互界面,通常用于执行系统任务、服务或守护程序。
精灵进程在 Linux 系统中被设计为长期运行的进程,独立于任何控制终端,并在系统启动时自动启动。它们通常在系统启动过程中由初始化进程(init process)启动,并在系统关闭时由系统管理器来终止。
以下是几个关于精灵进程的特征和使用场景:
无终端依赖:精灵进程不与任何用户终端关联,它们在后台默默地执行任务,不产生与用户交互的输出。
后台运行:精灵进程通常以守护程序(daemon)的形式运行,通过在启动过程中分离与终端的连接,使进程在后台持续运行,不受用户登录或注销的影响。
脱离会话:精灵进程通过调用 setsid() 函数脱离当前会话,成为一个新的会话组的组长进程,从而避免与终端会话相关的信号和控制。
日志记录:精灵进程通常将运行日志输出到指定的日志文件,以便系统管理者进行故障排查和性能监测。
系统服务:精灵进程常用于提供系统级别的服务,比如网络服务(如 Apache、Nginx)、数据库服务(如 MySQL、PostgreSQL)、邮件服务(如 Sendmail、Postfix)等。
通过将任务放入精灵进程中,可以使其独立于用户会话,在后台稳定地执行。这样可以确保系统服务持续可用,并提高系统的可靠性和安全性。
在 Linux 中,精灵进程的实现通常需要编写特殊的代码来处理相关的进程控制、信号处理、I/O 重定向等操作。同时,系统管理者还可以使用工具如 systemd、init.d 等来管理和监控精灵进程的启动、停止和状态等。
在linux或者unix系统中,用ps –l命令则会类似输出以下几个内容:
我们很容易注意到其中的几个重要信息,有下:
UID
: 代表执行者的身份PID
: 代表这个进程的代号PPID
:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号PRI
:代表这个进程可被执行的优先级,其值越小越早被执行NI
:代表这个进程的nice值在Linux中,进程调度有两个重要的参数PRI
和NI
。
PRI是指进程的调度优先级,取值范围从-20到+19,数值越小,优先级越高,表示进程会更早得到CPU的调度。默认情况下,新创建的进程的优先级为0。可以使用 nice
命令来改变进程的优先级。nice
命令可以修改进程的NI值,NI值与PRI相反,表示进程的“优先级偏移量”,数值越大,优先级越低。nice
命令的取值范围也是从-20到+19。
例如,当一个进程的PRI为15时,它比一个优先级为10的进程具有更高的优先级。当一个进程的NI为5时,它比一个NI为1的进程具有更低的优先级。
linux进程的优先级数值范围:60~99
linux中默认进程的优先级都是:80
linux是支持动态优先级调整的
linux进程pcb中存在一个nice值:进程优先级的修正数据;pri(新) = pri(old)+nice
old pri,都是从80开始的!
NI并不影响进程的实际执行顺序,只是影响进程在竞争CPU时的优先级,这对于一些需要在后台长时间运行的程序很有用。
为什么要把优先级限定在一定的范围内?
os调度的时候,必须要均衡的让每一个进程都要得到调度
不这么做,容易导致优先级较低的进程,长时间得不到cpu资源,导致——>进程饥饿
总之,PRI和NI是用于控制进程调度和优先级的两个参数。PRI值越小,进程的优先级越高,NI值越大,进程的优先级越低。这些参数在Linux系统中可以通过一些命令和调用设置或修改。
在Linux系统中,top
命令是一个常用的系统性能监测工具,通过实时查看进程占用CPU、内存等资源的情况,可以帮助用户了解系统的运行状态并及时发现问题。
top
命令的使用非常简单,只需要在命令行中输入 top
命令后回车即可。当top
命令执行后,将会显示系统当前的状态,包括系统负载、进程数、CPU和内存使用情况等信息。其中,以下是top
命令中常用的几个参数:
PID
: 进程IDUSER
: 进程所属用户PR
: 进程优先级NI
: 进程的"优先级偏移量"VIRT
: 进程使用的虚拟内存大小RES
: 进程使用的物理内存大小SHR
: 共享内存大小%CPU
: 进程占用CPU的使用百分比%MEM
: 进程占用内存的使用百分比TIME+
: 进程已经运行的时间在top
命令界面,用户可以通过按下不同的键来刷新进程、排序进程、更改刷新时间等。例如,按下 k
键可以终止指定的进程,按下 q
键可以退出top
命令。
在top中怎么更改已存在进程的nice?
在top
中,可以通过以下步骤更改已存在进程的nice
值:
top
命令界面,输入Shift + M
,将进程按内存使用排序。nice
值的进程,在该进程所在行中,记下其PID
。Shift + R
,然后输入要修改的nice
值(取值范围为-20到19),再输入修改的进程的PID
。注意,更改进程的nice
值需要具有管理员权限。同时,更改了进程的nice
值后,并不能立即看到效果,因为进程的优先级只有在竞争CPU时才会体现出来。
nice
是一个常用的命令行工具,用于调整进程的优先级。它可以改变进程调度时的优先级值,从而影响进程在系统中的运行顺序。比如,我们可以通过nice
命令将某个进程的优先级值调低,使得其他高优先级进程得到更多的CPU时间片,以提高系统的响应速度。
nice
命令的一般语法格式如下:
nice [OPTION] [COMMAND [ARG]...]
其中,OPTION
是可选参数,通常用于指定nice
值的大小和作用范围等;COMMAND
是需要执行的命令,可以是一个可执行文件或者shell脚本等;ARG
是传递给COMMAND
的参数。
当不指定OPTION
参数时,默认会将进程的nice
值设为10。nice
值越小,进程的优先级越高。下面是一些常用的nice
命令参数:
-n, --adjustment=N
: 指定nice
值的大小,其取值范围为-20到19,数值越小代表进程的优先级越高。-p, --pid=N
: 指定要调整优先级的进程ID。-g, --group=N
: 指定要调整优先级的进程组ID。-h, --help
: 显示帮助信息。比如,我们可以通过以下命令将进程PID
为1234的nice
值设为-5,提高该进程的优先级:
nice -n -5 -p 1234
需要注意的是,只有具有足够权限的用户才能通过nice
命令来调整其他用户的进程优先级。
renice
命令是用于重新设置正在运行的进程的调度优先级值的命令。和nice
命令不同,renice
命令可以对已经运行的进程进行动态的优先级调整,而无需重新启动进程。
renice
命令的一般语法格式如下:
renice [OPTIONS] PRIORITY [[-g | --pgrp] GID] [[-p | --pid] PID] [[-u | --user] USER]
其中,OPTIONS
是可选参数;PRIORITY
指定新的优先级值;GID
指定进程组ID;PID
指定进程ID;USER
指定用户名。
renice
命令常用的选项包括:
-n, --priority=NUM
: 指定新的优先级值。优先级值的范围是-20到19,数值越小代表进程的优先级越高。-g, --pgrp
: 指定进程组ID。-p, --pid
: 指定进程ID。-u, --user
: 指定用户名。下面是一些使用示例:
将进程ID为1234的进程的优先级值调整为10:
renice 10 -p 1234
将进程组ID为5678的进程组中所有进程的优先级值调整为-5:
renice -5 -g 5678
将用户名为"john"的所有进程的优先级值调整为0:
renice 0 -u john
并行和并发是两个与多任务处理相关的概念。
并行指的是同时执行多个任务的能力。在计算机领域,当系统具备多个处理器或多个核心时,可以将不同的任务分配给这些处理器或核心并行执行。每个处理器或核心都独立执行任务,从而提高了系统的整体处理能力。
并发指的是在同一时间段内同时执行多个任务的能力。在单核处理器
的情况下,系统通过快速切换任务的执行顺序来达到并发效果。操作系统会给每个任务分配时间片,并根据一定的调度算法动态地切换任务执行,使得多个任务看上去同时进行。
并行和并发之间的区别在于是否需要多个处理器或核心来实现。如果有多个处理器或核心,可以同时执行多个任务,这就是并行。而如果只有一个处理器或核心,通过任务切换来实现多个任务在同一时间段内执行,这就是并发。
需要注意的是,并行和并发并不一定真正意味着同时执行。在实际系统中,由于资源的有限性和调度的限制,任务可能会以交替、重叠或部分并行的方式执行。无论是并行还是并发,都旨在提高系统的效率和响应能力,使多个任务能够高效地执行。
时间片(Time Slice)是操作系统中的一个概念,用于描述多任务处理的调度方式之一。
在多任务处理中,操作系统需要为多个任务(进程或线程)分配处理器的使用时间。为了实现任务之间的并发执行,操作系统采用了时间片轮转调度算法。时间片是操作系统将处理器时间划分为固定大小的时间段,每个任务被分配一个时间片来执行。
当一个任务的时间片用完后,操作系统会中断该任务的执行,并将处理器分配给下一个等待执行的任务。这样,任务之间会依次轮流执行,每个任务都能获得一小段时间进行运行。
时间片的长度通常是固定的,由操作系统设置。典型的时间片长度在几毫秒到几十毫秒之间,具体取决于操作系统的调度策略和硬件性能。
通过使用时间片轮转调度算法,操作系统可以实现多任务并发执行的效果,使得各个任务看起来是同时进行的。这种调度方式可以提高系统的响应速度和资源利用率,避免某个任务占用处理器过长时间导致其他任务无法运行。
需要注意的是,时间片轮转调度算法并不保证所有任务获得相同的处理器时间,因为任务的优先级、阻塞情况等因素也会影响任务的执行顺序。其他调度算法如优先级调度、抢占式调度等也可以在不同场景下使用,以满足不同的需求。
竞争性
: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高独立性
: 多进程运行,需要独享各种资源,多进程运行期间互不干扰并行
: 多个进程在多个CPU下分别,同时进行运行,这称之为并行并发
: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为将cpu的寄存器数据保存到进程PCB中,本质:CPU寄存器的内容,保存到内存中!
寄存器的中保存的数据不会删除,只会在进程调度的时候,被新的数据覆盖。
一个CPU拥有一个runqueue
对于上图中的queue[140],队列中的元素其实就是一个个的task_struct* 的指针数组,即主要来存放PCB的地址。
这里的优先级关系我们先理解如下:
简单来说,就是OS在调度进程时,会从活动队列的queue[140]的0下标开始向后遍历,下标越小,优先级越高,直至遍历到一个非空队列,此时该位置的进程将会被调度到CPU资源中。
刚刚介绍了活动队列,我想大家心里肯定会有很多疑问:
(1):如果OS从活动队列中的queue[140]依据优先级进行进程调度,假如说有两个进程,一个优先级为1,一个优先级为2,当1在CPU中调度完之后,原本该轮到2了,但是此时,又插入了一个优先级为1的进程,此时抢占了2的调度,那么如果有很多优先级为1的进程不断抢占进程2的调度,就会导致进程2 的进程饥饿,这样怎么能实现CPU资源的合理分配呢?
(2):我们知道了进程中的时间片概念,还是和问题1一样,进程1和进程2,当进程1在CPU运行的时候,它的时间片结束了,此时要从CPU上剥离了,此时进程1要放在哪里呢?放在进程2在queue的位置或之后都不合理,因为我们要保证进程运行的优先级。
所以,我们引入了一个非常重要的概念————过期队列
🌈下面是过期队列的概念
过期队列
(expired queue),用于存储已经运行了一段时间的进程。这些进程可能已经超过其时间片的限制,或者因为等待某个事件而被阻塞。
当一个进程被插入到过期队列中时,它暂时不会被调度器考虑。然而,当活动队列中没有可运行的进程时,调度器将会检查过期队列,并把其中最老的进程作为下一个要运行的进程。这样做有利于保证每个进程都能够得到公平的执行机会。
过期队列同样使用了双向链表来管理其中的进程。但是与活动队列不同,过期队列并不区分 CPU,也就是说,所有 CPU 共享同一个过期队列。这意味着在一个 CPU 上过期的进程可能会被调度到另一个 CPU 上运行,而且由于 CPU 数量的变化,过期队列的长度也可能会发生变化。
需要注意的是,过期队列只包含长时间运行的进程,而短时间运行的进程则不会被放入过期队列中。这可以减少过期队列的长度,同时允许短时间运行的进程更快地被重新调度。
所以对于上述两个问题我们就可以得到解答了
(1):有进程进来插队?不好意思,先去过期队列的位置坐着
(2):时间片结束了?来,过期队列先坐着
当活动队列中的进程都结束后,再来到过期队列,将过期队列中的进程swap回活动队列中对应的优先级位置,这样既能保证进程得以执行完全,又可以让进程的优先级执行顺序得到保证。
Bitmap[5]通常是指一个包含5个位(bit)的位图(bitmap)。位图是一种数据结构,用于表示或存储一组二进制值,通常是0或1。每个位代表一个特定的状态或标记。
在一个5位的位图中,可以有32种不同的组合方式,因为2的5次方等于32。这意味着每个位能够表示32个不同的状态或标记。
位图通常用于节省内存空间,并提供高效的操作。通过使用位运算,可以在一个位图中快速进行各种操作,例如设置位、清除位、检查位等。
对于一个5位的位图,可以使用一个整数(如无符号整数或无符号字节)来表示。每个位对应整数中的一个比特位。例如,如果使用无符号字节表示,那么它的取值范围是0到255,而每个位则对应字节中的一个比特位。
以下是一个示例,展示了一个5位的位图以及可能的位操作:
位图: [0][0][0][0][0]
设置第3位: [0][0][1][0][0] (将第3位从0设置为1)
清除第2位: [0][0][0][0][0] (将第2位从1清除为0)
检查第4位: 该位为0
根据需求,位图可以扩展成更大的大小。较大的位图可以表示更多的状态或标记,但也会占用更多的内存空间。
——————————————————————————
在我们上面说的queue[140],:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率!
main
函数的前两个参数 int argc
和 char *argv[]
用于获取命令行参数。
argc
(Argument Count)是一个整数,表示命令行参数的数量。它包括程序名称本身作为第一个参数,因此 argc
的值至少为1。
argv
(Argument Vector)是一个字符串指针数组,每个元素指向一个命令行参数的字符串。其中 argv[0]
是程序的名称,后续的 argv[1]
、argv[2]
、依次类推,是传递给程序的其他参数。
下面是一个示例代码,展示如何使用 argc
和 argv
获取命令行参数:
#include
int main(int argc, char *argv[]) {
printf("程序名称:%s\n", argv[0]);
if (argc > 1) {
printf("其他参数:\n");
for (int i = 1; i < argc; i++) {
printf("%s\n", argv[i]);
}
} else {
printf("没有其他参数。\n");
}
return 0;
}
在这个例子中,我们首先使用 printf
函数输出 argv[0]
,即程序的名称。然后,通过判断 argc
的值,来确定是否存在其他参数。如果存在其他参数,我们使用一个 for
循环遍历 argv
数组,并用 printf
函数输出每个参数的字符串。
当你编译并运行这段代码时,可以在命令行中传递参数,如 ./program arg1 arg2
,程序将输出相应的信息。
在main函数中,存在着两个参数,一个是命令参数数量argc
,一个就是指向存储命令参数地址的指针argv
如图上所示,我们的命令行参数一共有5个,全部都存储在argv中。
那么这有什么用呢? 🤔
我们通过下面这个例子来认识一下:
从这个例子我们可以理解,命令行参数,可以支持各种指令级别的命令行选项的设置
环境变量是操作系统中一种用于存储和传递配置信息的机制。它们是一些特定名称和对应值的键值对,用于在操作系统和其运行的应用程序之间传递参数或配置信息。
我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但
是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找
每个操作系统都有自己的环境变量系统,例如在 Windows 中使用的是系统环境变量,而在类Unix系统(如Linux、macOS)中使用的是shell环境变量。
环境变量可以包含各种类型的数据,例如路径、用户名、临时文件位置等,并且它们在操作系统中全局可访问。应用程序可以读取环境变量来获取关于系统或用户配置的信息,从而做出相应的调整或决策。
以下是一些环境变量的常见用途:
PATH
:指定可执行程序的搜索路径,当你在命令行输入一个命令时,系统会在这些路径下查找可执行程序。 HOME
(Windows中通常为USERPROFILE):指定用户的主目录,一些程序会使用它来确定默认的配置文件位置。JAVA_HOME
:指定Java开发工具包(JDK)的安装路径,许多Java相关程序依赖这个环境变量。TEMP
(或TMP):指定操作系统用于存储临时文件的目录路径。USER
(或USERNAME):指定当前登录用户的用户名。可以通过操作系统提供的特定命令或工具来管理环境变量。例如,在Windows中,可以使用系统属性对话框或set
命令来设置和查询环境变量;在Linux和macOS中,可以使用export
和printenv
等命令来进行操作。
环境变量在软件开发、系统管理和运维等领域中扮演着重要的角色。通过使用环境变量,可以方便地配置和定制应用程序的行为,提高系统的灵活性和可配置性。
在C语言中,getenv()
函数可以用来获取指定名称的环境变量的值。其函数原型定义在
头文件中,具体语法如下:
char *getenv(const char *name);
其中,name
参数是要获取的环境变量的名称,返回值是该环境变量的字符串值。
例如,如果要获取环境变量PATH
的值,则可以使用以下代码:
#include
#include
int main() {
char *path_value = getenv("PATH");
if (path_value) {
printf("PATH=%s\n", path_value);
} else {
printf("PATH environment variable not found.\n");
}
return 0;
}
上述代码中,getenv("PATH")
函数会返回环境变量PATH
的字符串值,并将该值赋给path_value
指针变量。如果环境变量存在,则输出该变量的值,否则输出一条错误信息。
需要注意的是,getenv()
函数返回的字符串指针指向的是静态内存区域,因此不允许修改或释放该指针指向的内容。可以复制一份该字符串并重新分配一个新的内存空间,以便安全地使用该字符串。
在Linux中,可以通过main
函数的第三个参数 int* env[]
来获取环境变量。这个参数是一个字符串指针数组,每个元素指向一个环境变量字符串。
下面是一个示例代码,展示如何使用 env
参数获取环境变量:
#include
int main(int argc, char *argv[], char *env[]) {
// 遍历环境变量并输出
for (int i = 0; env[i] != NULL; i++) {
printf("%s\n", env[i]);
}
return 0;
}
在这个例子中,我们使用了一个 for
循环来遍历 env
数组,直到遇到值为 NULL
的元素为止。在循环体内,我们使用 printf
函数来输出每个环境变量的字符串。
当你编译并运行这段代码时,它将输出当前进程的所有环境变量。
需要注意的是,每个环境变量字符串的格式是 变量名=值
。例如,PATH=/usr/local/bin:/usr/bin:/bin
。
在Linux中,可以使用 extern char** environ
来获取环境变量。environ
是一个指向字符串指针数组的全局变量,它包含了当前进程的所有环境变量。
下面是一个示例代码,展示如何使用 environ
变量获取环境变量:
#include
extern char** environ;
int main(int argc, char *argv[]) {
// 遍历环境变量并输出
for (int i = 0; environ[i] != NULL; i++) {
printf("%s\n", environ[i]);
}
return 0;
}
在这个例子中,我们使用了一个 for
循环来遍历 environ
数组,直到遇到值为 NULL
的元素为止。在循环体内,我们使用 printf
函数来输出每个环境变量的字符串。
当你编译并运行这段代码时,它将输出当前进程的所有环境变量。
虽然 environ
是标准的方式来获取环境变量,但它通常被视为不够安全,因为这是一个全局变量
,可能会被其他代码修改。因此,在实际开发中,最好使用 getenv
函数来获取特定环境变量的值,以保证安全性。
将可执行程序加入到PATH中是让系统能够在任意位置访问该程序的一种常见方法。以下是向PATH中添加可执行程序的步骤:
找到可执行程序的路径,例如/path/to/executable
。
执行以下命令将该路径添加到PATH环境变量中:
export PATH=$PATH:/path/to/executable
这个命令会将PATH环境变量当前的值和新的路径组合起来,并存储回PATH环境变量中。
为了使该命令永久生效,可以将上述命令添加到系统启动文件中,例如在.bashrc
或.bash_profile
中加入以下行:
export PATH=$PATH:/path/to/executable
这样每次打开终端时,都会自动将新路径添加到PATH环境变量中。
重要提示:由于PATH环境变量控制着系统中所有可执行程序的搜索路径,因此需要保证该变量只包含安全且必要的路径。不当的修改PATH环境变量可能导致系统风险或安全问题。建议谨慎操作并使用最小化原则。
🌈查看环境变量方法
echo $NAME //NAME:你的环境变量名称
这些命令后续会补充说明!
在Linux系统中,env
是一个常用的命令行工具,用于显示、设置和执行环境变量。它可以用于查看当前环境变量的值,以及在运行命令时设置特定的环境变量。
以下是env
命令的常见用法:
显示当前环境变量:
env
这会列出当前会话的所有环境变量及其对应的值。
显示特定环境变量的值:
env <variable_name>
将
替换为你要查看的环境变量的名称,这样会输出该环境变量的值。
设置环境变量并运行命令:
env <variable_name>=<value> <command>
将
替换为要设置的环境变量的名称,
替换为其对应的值,
替换为要执行的命令。这样会将指定的环境变量设置为给定值,并在执行该命令时生效。
清除一个或多个环境变量:
env -u <variable_name> <command>
使用 -u
选项来清除指定的环境变量,
替换为要清除的环境变量的名称,
替换为要执行的命令。这样会在执行该命令时移除指定的环境变量。
env
命令可以方便地控制和管理环境变量,对于在特定环境下运行程序或执行命令时设置和修改环境变量非常有用。使用man env
命令可以查看更详细的env
命令用法和选项说明。
在 Linux 中,echo
是一个用于向标准输出(STDOUT)打印一行或多行文本的内建命令。它的主要作用是将指定的字符串输出到屏幕上,以便用户查看。
echo
命令的基本语法如下:
echo [option(s)] [string(s)]
其中,option(s)
是可选的选项,用于控制输出格式,string(s)
是要输出的字符串。
常用的 echo
选项包括:
-n
:不在输出末尾添加换行符;-e
:支持反斜杠(backslash)转义字符;-E
:禁用反斜杠转义。例如,要输出字符串 “Hello, World!” 并换行,可以使用以下命令:
echo "Hello, World!"
如果要同时输出多个字符串,可以用空格分隔它们:
echo "Hello," "World!"
需要注意的是,如果字符串中包含某些特殊字符,可能需要使用反斜杠进行转义。常见的转义字符包括:
\n
:换行;\t
:制表符;\"
:双引号;\\
:反斜杠本身。例如,要输出字符串 “Hello, World!” 并在两个单词之间加上制表符,可以使用以下命令:
echo -e "Hello,\tWorld!"
与 env
命令不同,echo
不会显示环境变量。env
命令用于显示当前 Shell 进程中的所有环境变量以及它们的值。例如,env USER
可以显示当前用户的用户名。需要注意的是,env
命令所显示的环境变量并不包括本地变量。
总结一下,echo
命令用于向标准输出打印一行或多行文本。它可以输出一个或多个字符串,并且支持一些选项和转义字符。与 env
命令不同,echo
不会显示环境变量但可以显示本地变量。
在 Linux 中,set
是一个用于显示、配置和修改 Shell 解释器运行时行为的内建命令。它可以用于查看当前 Shell 的各种设置和环境变量,并且还可以对一些设置进行修改。
set
命令有不同的使用方式和选项,下面列举了一些常用的用法:
显示所有变量和函数:
set
运行 set
命令(无参数)可以列出当前 Shell 的所有变量和函数,包括环境变量、本地变量、位置参数等。
显示特定类型的变量:
set -o
使用 set -o
命令可以列出当前 Shell 中的所有选项和设置。
设置选项和开关:
set -o option
set +o option
使用 set -o
命令可以打开某个选项,使用 set +o
命令可以关闭某个选项。其中,option
是具体的选项名称,比如 set -o vi
可以启用 Vi 编辑模式。
修改变量值:
set variable=value
运行 set
命令并指定变量名和新值,可以修改或创建一个本地变量。
设置位置参数:
set -- argument1 argument2 argument3
使用 set --
命令可以重新设置位置参数。这在编写脚本时非常有用,可以在脚本中动态修改传入的参数。
除了上述常用的用法之外,set
命令还有其他一些选项和功能,可以通过 set --help
或 help set
来查看详细的帮助文档。
需要注意的是,set
命令所做的修改只对当前 Shell 会话有效,不会影响其他 Shell 实例。如果要使设置永久生效,可以将其添加到 Shell 配置文件中(如 .bashrc
或 .bash_profile
)。
总结一下,set
命令用于显示、配置和修改 Shell 解释器运行时的行为和变量。它可以显示当前 Shell 的各种设置和环境变量,也可以修改一些设置或创建本地变量。set
命令的具体用法取决于所使用的选项和参数。
子进程的命令行参数和环境变量是在创建子进程时由父进程传递给子进程的。
命令行参数:父进程可以通过在创建子进程时设置子进程的命令行参数来传递信息。在C语言中,这些命令行参数通常是通过 exec
系列函数来执行新程序时传递的。例如,使用 execve
函数时,可以通过将命令行参数以字符串数组的形式传递给子进程。
环境变量:父进程还可以通过设置子进程的环境变量来传递信息。环境变量是一个键值对的集合,它存储了一些全局的配置和信息。在C语言中,通过在 exec
系列函数调用中传递一个字符串指针数组来设置子进程的环境变量。每个字符串都遵循 变量名=值
的格式。
当子进程开始执行时,它会继承父进程的命令行参数和环境变量。子进程可以使用相应的系统调用或库函数来访问和操作这些数据。
总结起来,父进程可以通过设置子进程的命令行参数和环境变量来传递信息,从而影响子进程的行为和配置。
在 Linux 中,父进程的环境变量信息来自于 shell 环境。当你在终端上执行命令时,实际上是由 shell 进程(通常是 Bash)来解析和执行这些命令。而父进程的环境变量就是 shell 自身的环境变量。
当一个子进程被创建时,它会继承父进程的环境变量。这意味着子进程将拥有与父进程相同的环境变量集合。
通常,在启动 Linux 终端时,shell 会读取系统的全局配置文件(如 /etc/profile
、/etc/bashrc
等),并将其中的环境变量加载到自身的环境中。此外,shell 也可以读取用户的个人配置文件(如 ~/.bash_profile
、~/.bashrc
等)并继续加载更多的环境变量。这些配置文件中的环境变量会成为父进程的环境变量,从而传递给它所创建的子进程。
需要注意的是,父进程的环境变量是一种静态状态,即在父进程启动时确定,并不会随后的环境变量的修改而改变。因此,如果你在终端中修改了环境变量,只有在重新启动新的 shell 进程时,子进程才会继承到更新后的环境变量。
总而言之,父进程的环境变量信息来自于启动终端时读取的全局和个人配置文件,子进程继承了父进程的环境变量。
在 Linux 中,本地变量(也称为局部变量)和环境变量是两种不同的变量类型,它们的作用范围和使用方式有所不同。
本地变量:
variable_name=value
的语法进行定义。echo $变量名
来访问其存储的值。环境变量:
作用范围:环境变量是全局性的,对整个系统及其衍生的进程都可见。即使在不同的代码块或进程中,环境变量的值都是一致的。
定义方式:环境变量通常在启动进程时由父进程传递给子进程,并通过操作系统提供的特定接口来设置。例如,在 Bash 中可以使用export variable_name=value
的语法将一个本地变量导出为环境变量。
访问方式:环境变量可以在任何进程中直接使用,通过变量名来访问其存储的值。在 Bash 中,可以使用$variable_name
或${variable_name}
的方式来引用环境变量。
总结起来,本地变量只在bash进程内部有效,不会被子进程继承下去,而环境变量通过让所有的子进程继承的方式,实现自身的全局性。
在 Linux 中,命令可以分为两类:常规命令和内建命令。
常规命令
:
/bin
、/usr/bin
等)。内建命令
:
一些常见的内建命令包括:
与常规命令相比,内建命令的执行速度更快,因为它们直接在 Shell 内部运行,无需创建新的进程。
要确定一个命令是常规命令还是内建命令,可以使用 type
命令。例如,type cd
将显示该命令是一个内建命令。
总结一下,常规命令是以可执行文件形式存在的外部命令,而内建命令是 Shell 解释器内部提供的命令。常规命令需要启动新的进程来执行,而内建命令直接在 Shell 内部进行解析和执行。
在 Linux 中,export
是一个用于设置环境变量并将其导出到子进程的内建命令。它的主要作用是将一个本地变量导出为一个环境变量,使得该环境变量在当前 Shell 及其衍生的子进程中可见和可用。
export
命令的基本语法如下:
export variable_name=value
其中,variable_name
是要设置的环境变量的名称,value
是要赋给环境变量的值。
使用 export
命令可以完成以下操作:
设置环境变量:
export MY_VAR="Hello"
这样就创建了一个名为 MY_VAR
的环境变量,并将其值设为 “Hello”。这个环境变量将在当前 Shell 中可见。
导出本地变量:
LOCAL_VAR="World"
export LOCAL_VAR
这里首先定义了一个本地变量 LOCAL_VAR
,然后使用 export
命令导出它,使之成为一个环境变量。这样,LOCAL_VAR
将在当前 Shell 及其子进程中都可见。
查看已导出的环境变量:
export
运行 export
命令(无参数)可以列出当前 Shell 中所有已导出的环境变量及其值。
需要注意的是,通过 export
命令设置的环境变量只在当前 Shell 及其子进程中有效。在新的终端会话或 Shell 中,这些环境变量需要重新设置。
此外,可以使用以下命令取消导出(删除)一个环境变量:
unset variable_name
例如,unset MY_VAR
将取消导出并删除名为 MY_VAR
的环境变量。
总结一下,export
命令用于设置本地变量并将其导出为环境变量,使得该环境变量在当前 Shell 及其衍生的子进程中可见和可用。通过 export
命令设置的环境变量可以通过 export
命令查看,并且可以使用 unset
命令取消导出(删除)一个环境变量。
我们观察上图,发现堆区和栈区是双向奔赴
的。
在程序地址空间中,栈区存储是高地址向低地址,堆区存储是低地址向高地址,它们存储数据会不会相遇起冲突? 🤔
在程序地址空间中,栈区和堆区确实是分别位于高地址和低地址。一般来说,栈区向下增长(即栈顶地址不断减小),堆区向上增长(即堆底地址不断增加)。
由于它们是两个独立的区域,存储数据时不会发生冲突。栈区和堆区是由操作系统所管理的虚拟地址空间
,每个区域都会被分配一段连续的虚拟地址,因此它们的地址范围是不会相交的。
在程序运行时,栈区和堆区都有自己的内存使用规则,也都有一些限制条件。例如,栈区的大小和深度可能受到硬件和操作系统的限制,递归调用过深或者申请过多内存都可能导致栈溢出;堆区的大小通常比较灵活,但需要手动申请和释放内存,如果管理不当就可能导致内存泄漏等问题。
在编写程序时,需要谨慎地使用栈和堆,避免出现内存错误和安全漏洞。需要注意的是,栈区和堆区占用的不只是虚拟地址空间,还需要依赖物理内存进行支持,因此在大量使用栈和堆时可能会影响程序的性能和稳定性。
总之,栈区和堆区是两个独立的内存空间,它们存储数据时不会相遇起冲突。在编写程序时需要遵守栈和堆的使用规则,避免出现内存错误和安全问题,并且注意内存使用的效率和稳定性。
虚拟地址空间是操作系统为一个进程所提供的抽象地址空间,它将物理内存和磁盘空间等多种资源组合成一个连续的、线性的地址空间,为进程提供了一种独立于物理硬件的编程环境。
在虚拟地址空间中,每个进程都有自己的地址空间,其大小通常为32位或64位。这个地址空间通常被分为多个段,其中包括代码段、数据段、堆区、栈区等,每个段都有自己的起始地址和大小,用于存储程序代码、全局变量、动态分配的内存等。
虚拟地址空间的设计基于两个主要目标:
提供安全性。每个进程都有自己独立的地址空间,因此不同进程之间的数据互不干扰,可以有效地保护各个进程的数据安全。
提高效率。虚拟地址空间把物理内存和磁盘等多种资源组合成一个连续的地址空间,允许进程访问超过物理内存大小的数据,从而提高了系统的总体效率。如果某些数据没有被使用,操作系统可以将其置换到磁盘上,以便给其他应用程序腾出更多内存空间。
虚拟地址空间的实现需要借助硬件支持。通常,操作系统会将虚拟地址翻译成物理地址,让CPU访问真正的内存。这个过程是由MMU(Memory Management Unit)来完成的,它通过使用页表等机制将虚拟地址翻译为物理地址,以便CPU访问对应的内存。
总之,虚拟地址空间是一种抽象地址空间,为进程提供了一种独立于物理硬件的编程环境。它将物理内存和磁盘空间等多种资源组合成一个连续的、线性的地址空间,为进程提供数据安全性和效率上的保障。虚拟地址空间的实现需要借助硬件支持,通过MMU将虚拟地址翻译为物理地址,以便CPU访问真正的内存。
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长