初始Linux—Linux系统编程第三节——初始进程

简介: 在说冯诺依曼体系结构之前,我们先来了解这么一个常识:我们的电脑或者手机,总的来说,其体系结构都是由 软件+硬件 构成。

目录


冯 · 诺依曼体系结构


操作系统:Operator System(OS)


进程的基本概念


进程标识符


通过系统调用创建进程-fork初识


进程状态


僵尸进程


孤儿进程


进程优先级


环境变量


和环境变量相关的命令


环境变量的组织方式


main函数的三个参数


冯 · 诺依曼体系结构

在说冯诺依曼体系结构之前,我们先来了解这么一个常识:


我们的电脑或者手机,


总的来说,其体系结构都是由 软件+硬件 构成。


而硬件部分,有 像我们所说的磁盘、键盘、网卡等等硬件设施,构成整体的硬件框架结构。


而软件部分,最核心、最重要的,就是我们的操作系统了。


软硬件结合,构成我们的计算机的体系结构。


就像下图所示:

image.png




那么,我们的计算机是怎么将软件和硬件结合在一起的呢?


这个时候,我们就需要来了解一个体系结构——冯诺依曼体系结构


我们基本所有的计算机系统,都遵循这样这么个结构。


这个结构其实也很简单,其可以用图示表示成这样:

image.png



首先,我们可以知道,


输入设备一般可以是包括  键盘,硬盘,网卡,鼠标,扫描仪, 写板等

输出设备一般可以是包括  显示器,打印机,硬盘,网卡等


解释一下,这里有的设备既可以作为输入设备,也可以作为输出设备。


比如刚刚所举的 网卡,硬盘 等。其实很好理解,待会我们下面会举例。


而这里的存储器,指的就是内存


运算器,可以简单理解为就是用于计算的那些东西(简单举例子,就是加减乘除等等)


控制器,可以理解为 作用是控制着 数据信号 传导


我们一般习惯把 输入设备和输出设备 统称为 外设。


我们从这个图中可以得到这样一些看似简单,但是很有用的信息:


不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)

外设(输入或输出设备)要输入或者输出数据,也只能取写入内存或者从内存中读。

一句话,所有设备都只能直接和内存打交道。

说的直白一点,CPU是内部,而外设是外部,内部和外部要建立联系,那就必须要经过哨兵——内存。


再或者说,CPU是女方,外设是男方,而男方和女方想要牵手成功,就需要媒婆,而该内存就是扮演了媒婆这样一个角色。


理解到这个意思就行。


那有人会问,为什么要有内存呢?


难道就不能让CPU直接从外设读取信息,然后处理吗?


我们来回答一下这个问题:

image.png



我们结合这一张图来说明:


在金字塔顶端的说明其价格贵,在底端的说明其价格便宜。


在金字塔顶端的说明其运算速度快、效率高,在底端的说明其运算速度较慢、效率低。


我们讲得更加直白简单一点:


在CPU中,运算速度都是以纳秒为计算单位的;


在内存中,运算速度是以微秒为计算单位的;


在硬盘(SSD固态硬盘等)中,运算速度是以毫秒为单位的。


如果没有内存,那么CPU的运算速度是那么高,但是呢,硬盘的运算速度相较而言又是那么低,你们猜,最终的运算速度是要依照谁来定呢?


当然是硬盘。


这就类似于木桶原理。


可能CPU在运算的时候,0.001%的时间用来计算,99.999%的时间都是在用来等待了。


这样的话,不仅计算的效率变低,而且由于大部分的时间都在用来等待,会造成极大的浪费。


可是CPU又是那么的贵,所以CPU就总是只有那么一点点。


这样,便诞生了内存。在性价比的方面,做了一个折中。


这样,使得计算机的运算效率不仅不会那么低,并且还使得计算机不会那么贵,让普通人也能够用得起。


当然,在CPU中还有着寄存器、缓存等概念,它们的主要作用也同样是提高运算效率来的。


就好比你跟不上我,那我就先放在缓存(或者寄存器),然后我继续做我的事,不至于让我在原地静静地傻等。


好。我们解释完了该体系中的相关的常识性概念之后,我们再来用动态的眼光,看看这个体系结构:


我们先从硬件的角度来看:


想要数据信号从输入设备进入到输出设备中,


我们可以认为:


其先是经过了输入缓冲区(对标你的scanf在输入一行数据的时候,其就是先加载到了输入缓冲区中),当你敲下回车的那一瞬间,缓冲区刷新,数据就被加载到了内存(也就是存储器)中。(当然,刷新缓冲区的方式不仅仅有按回车这一种方法)


然后内存再将数据传给CPU,让CPU处理。


数据信号从CPU中出来,先进入内存当中,然后先是进入输出缓冲区中,也叫预写入


当程序终止、或者是遇到 ‘\n' 、fflush函数 等时,缓冲区刷新,就会将数据载入到输出设备当中。


简单比划一下就是这样:

image.png



如果从软件的角度来去看,这个工作是由谁来做的呢?


答案是:操作系统。(Operator System  简称OS)


也就是说,是操作系统完成数据的加载、输出等等工作。我们接下来,就会详细地介绍它的作用和功能。


这也从另一个角度来说就是:从软硬件的角度,内存的存在、缓存的特性都是可行的。


我们来通过举一个例子的方式,即解释从你登录上qq开始和某位朋友聊天开始,数据的流动过程。


我们忽略网络细节。

image.png



简单图示如上。请看下面的详细过程:


当你输入一条消息,没有发送的时候,可以认为其实在硬盘或者是输入缓冲区中,消息发送后,其 先会载入到内存中,(这其实也与你的QQ一直都是处于运行状态,即一直在内存运行中相匹配)紧接着,经过你的电脑的CPU处理,再经过内存,输出到你的网卡当中;


然后,通过网络(这里我们先忽略网络相关细节),传送到你的同学的电脑的网卡当中。数据通过你同学电脑的网卡,加载到你同学电脑的内存当中,经过CPU处理,再经过内存,输出到你的同学的电脑的显示器上。


在这里,对于你的电脑的主机而言,你的键盘相当于输入设备,网卡相当于输出设备;


你的同学的电脑而言,他的网卡相当于输入设备,而显示器则相当于输出设备。


好,对于冯诺依曼体系结构,我们暂时先说到这里。


操作系统:Operator System(OS)

我们下面继续来说操作系统。


首先,我们需要了解什么是操作系统:


任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统来说,其包括内核(进程管理,内存管理,文件管理,驱动管理)和其他程序(例如函数库, shell程序等等)


说人话,就是操作系统是一套用来搞管理的软件。


管理什么?


管理软硬件


-> 硬件:包括冯诺依曼中的所有设备;


->软件:安装、卸载等,在系统层面,其包括文件、进程、驱动;


下面是一个比较好的管理分层的图的例子。

微信图片_20221209211337.png



我们结合这一张图, 来举一个例子:

微信图片_20221209211403.png



我们把操作系统看成是校长,底层的硬件看作是学生,那么驱动程序的存在就相当于是充当了导员的角色。


校长和学生,一般不见面,通过导员进行管理。但是,校长(操作系统)可以通过导员(驱动系统)拿到学生(底层硬件)的信息。


在这简单的层级关系里,校长(操作系统)就是来管理的,管理整个学校(整台计算机系统),用有决策权——简单理解可以认为是学生在哪一个班,正常上学或者勒令退学(对内存中的进程、文件等掌有生杀等等大权)


而当信息量和庞大的时候,校长(操作系统)就会将所有的学生的信息都描述起来(由于LInux就是用C写的,所以其用的就是结构体来完成的),然后可以通过链表等数据结构的方式,来将这些学生组织起来。


用6个字,就是:


先描述,再组织。


总结 一下,就是要:


描述被管理对象

组织被管理对象


1. 描述起来,用struct结构体

2. 组织起来,用链表或其他高效的数据结构


最后补充一点:为什么要存在OS,OS的存在有什么样的意义。


OS是为了与硬件交互,管理所有的软硬件资源、为用户程序(应用程序)提供一个良好的执行环境这样的需求而诞生的。你想一下,如果没有OS,你打个lol,2s退出一次,1s电脑重启一次...你还玩个锤子哈


再简单说一下系统调用和库函数的概念,这个我们后面还会继续说:


在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。说人话,就是有操作系统给我们提供的接口。(比如我们下面的fork函数,后面将要学习的exec系列的函数等)


系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。说直白点,就是我们的prinf,scanf等等,都算是这样经过二次开发后提供给用户使用的。


进程的基本概念

课本概念:程序的一个执行实例,正在执行的程序等

内核观点:担当分配系统资源(CPU时间,内存)的实体


还是说人话,我们从两个方面去解释:


1、类比刚刚操作系统的管理,那么操作系统对于进程的管理,


依然遵循六字原则:先描述,再组织。


2、对于一个普通的文件而言,其有文件属性(即文件大小、所有者、创建日期等等)+文件内容。


那么类比于进程,其也是包含了两方面:进程属性+对应的文件。


结合我们刚刚说的,一个程序文件加载进了内存,就变成了进程。


那么这个进程是就会被操作系统管理。


如果同时存在了多个进程,那么这些进程就会通过”先描述,再组织“的方式被管理。


那操作系统是如何来描述这些进程的呢?


答案是:还是用一个结构体来完成。其有专门的名称——PCB(Process Control Block)


通过查看Linux源码,我们会发现,有一个叫做task_struct的结构体,专门用来完成该项工作。

微信图片_20221209211423.png



PCB相较于task_struct的概念,就好比这样(如下图)

image.png




那么,我们现在对于 进程的认识可以这样来说明:


进程,就是可执行程序和需要管理进程的数据结构的集合


task_ struct内容分类


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

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

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

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

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

上下文数据: 进程执行时处理器的寄存器中的数据。(我们之后会再说)

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

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

其他信息

接下来,我们就要详细地挑选task_struct里的重要内容来进行讲解。


不过在此之前,首先,我们来说一下,如何查看进程:


有两种方式都可以:


1、进程的信息可以通过 /proc 系统文件夹查看



如:要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹


2、大多数进程信息同样可以使用top和ps这些用户级工具来获取


举个例子:


我们写一个死循环好了,目的就是让该程序一直运行下去。

然后让其编译运行,就会在屏幕上不停地打印hello Linux!

我们复制一个会话

这样,等会程序运行的时候,我们就能够检测到了。

好,现在我们让程序开始运行。

解释一下,这里,我们是用ps指令,来去查看进程的状态


关于ps的选项,参见下图:

image.png



然后后面的grep就是过滤一下要或者不要的信息。


读者可以自行尝试一下如果不过滤会出现什么样的效果。


关于程序进程的相关选项,我们下面会说。


进程标识符

对于一个进程,OS为了能够方便识别它们,在创建PCB的时候,每个人都会给其一个id。这就是进程标识符。


对于标识符,我们有pid和ppid之分。


pid指的是子进程,


ppid指的是父进程。


我们在程序中可以通过getppid() 和 getpid() 来实现。


我们还是通过一个小程序来举例:


(运行结果)


我们不难发现,这里的ppid和pid都是一串数字,实际上就是编号。


其中ppid指的是父进程,pid指的是子进程。


我们等会可以结合下面fork函数的例子来看。


通过系统调用创建进程-fork初识

我们这里仅仅是了解一下,fork怎么用,达到会用的标准就行。


先来介绍:


我们输入


man fork



可以看到,fork没有参数,返回值为pid_t的类型,作用是创建一个子进程。头文件为


实际上,我们需要知道,fork是有两个返回值的。


为什么呢?


我们可以这样来理解一下:

image.png



同时,需要注意:子进程的返回值是0;而父进程的返回值是子进程的id。


我们来看这样一个程序(来看实操):

 


总共9行代码


我们来看运行结果。



我们会发现,第二个printf的内容执行了两次。因为子进程在fork里创建后,子进程和父进程都会执行第二个printf。


也就是说,子进程和父进程是独立运行的。


那我们再来举一个例子,来看:


那么这下我们让代码执行,其会产生什么结果呢?


由于父子进程我们无法准确让谁先跑,谁后跑,所以我们加上一个sleep来以示区分。


我们会发现一个现象:子进程的ppid就是父进程的pid。这样也就能说明一个问题——为什么有父子这样的叫法了。


那父进程的ppid又是谁的呢?


答案是-bash的,就是操作系统的。(bash相当于操作系统手下的一个助手)


而bash也是一个进程。如下图


那bash能不能挂?


当然不能!bash是不能挂的!


而由bash通过创建子进程的形式,和我们刚刚用fork创建的子进程的形式是基本一致的。


进程状态

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)

我们可以来看一看Linux内核源代码里是怎样定义的:

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


R运行状态(running) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。(解释一下,R状态表明的就是运行状态,但其不一定表明的就是正在运行)


S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠

(interruptible sleep))。(也就是等待状态)


D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。(处于D状态的操作系统是杀死不了的,而如果大量的程序由于在IO而进入D状态,很有可能会使整个服务器崩溃——过年抢红包,偶尔的王者荣耀的服务器崩溃就和这有关)


T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。


X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。


我们可以通过这样的指令来查看进程状态:

ps aux 
ps axj  //上面的或者下面的


像我们刚刚看到的,比如:


通过这里的我们能够看到,我们这里的hello程序是处于一个休眠的状态。


这与IO等待有关系,因为IO是要和硬盘显示器发生交互,前面说过这个过程是很慢的, 就是说其0.1秒在运行,0.9秒都在等待,所以当你在查看其状态的时候,其处于S状态——即休眠状态。


 我如果把printf的内容去掉,就是让它一直死循环,就直接这样:

我们再来看:

我们让其运行,



我们看到,其为R状态,就是可以理解为是running 状态。这里的+表示其实在前台运行的,就是说我按  ctrl c 可以将程序终止


如果我要在后面加上&,其就变成是在后台运行的了。

这个时候,我们再按ctrl c,然后再调用后台观察,我们可以看到,R后面的+小时了,并且无论我们怎么按,其都是无法停止。

这个时候,我们如果想要终止它,可以用kill命令



后面的27459就是当前进程的进程编号。


我们这个时候再看,就会发现进程没了。

image.png


这个图,可以参考一下,看中文就可以了。


我们再来说两种特殊的进程——僵尸进程和孤儿进程


僵尸进程

——特殊的Z状态:


僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程

僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。

所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

我们来举一个例子:

微信图片_20221210111006.png

可以看到,按照我们的思路,子进程会在5秒之后退出,然后父进程是一个死循环。我们来看看这两个的进程在运行过程中的运行状态是怎样的。


我们编译后 ./hello让其执行

在另外的 一个窗口中,我们制作一个脚本,用于每1秒查看其进程状态

脚本如下:

while :; do ps aux | grep hello | grep -v grep;sleep 1; echo "#################";done


我们从上面的监视可以看出,子进程一开始和父进程一样,都是S状态,几秒钟后,其变成了Z状态。这里的Z就是僵尸状态。


那么为什么会有僵尸状态呢?


原因很简单。我们在创建一个进程的时候,是操作系统创建的。但是,进程在结束时,也需要有人来“收回”。


所以说,这里的子进程就是在等着父进程将其收回,读取它的退出状态。所以一直就是处于僵尸状态。


进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。


可父进程如果一直不读取,那子进程就一直处于Z状态?是的!


维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说, Z状态一直不退出, PCB一直都要维护?是的!


那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的。


因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间。


所以僵尸状态会导致什么?


就是我们之前强调很多遍、耳熟能详的——会导致内存泄漏。


那如何避免呢?实际上可以用wait。这个我们后面再说到进程等待的时候再说。


孤儿进程

孤儿进程,实际上和僵尸进程是两个方面,结合“孤儿”以及我们刚刚所说,不难猜出,僵尸进程是父进程一直在干活,子进程先挂了;那孤儿进程就是父进程先没了。


我们不再做过多的赘述,简单理解就是父进程没了,子进程还在。


有兴趣的读者可以自行尝试一下。


那么孤儿进程难道就没有父进程了吗?


答案并不是这样的。


这个时候,往往操作系统会来帮你。


将你的孤儿进程领养。


被谁领养?


一般都是一号进程。


孤儿进程被1号init进程领养,当然要有init进程回收喽。


进程优先级

我们说,进程的运行和办事情一样,也是有先后缓急之分的。


哪个进程先执行,哪个进程后执行,这是由其优先级所决定的(注意和权限区分一下,权限是能不能的问题,而优先级是已经能的基础上先后的问题)


我们如果输入ps -l,


会看到这样的信息:

这里的PRI是最终的PRI,是影响优先级的重要因素。一般而言,PRI越小,优先级越高,PRI越高,则相反。


解释一下其他几个有价值的值是什么意思:(其实我们部分在上面已经说过了)


UID : 代表执行者的身份

PID : 代表这个进程的代号

PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号

PRI :代表这个进程可被执行的优先级,其值越小越早被执行

NI :代表这个进程的nice值


而PRI是怎么算的呢?


我们可以这样认为:


PRI (最终) =  PRI(开始)+ NI(这里的NI意为nice值)


而PRI(开始)的值基本上都是80。我们在上面看到的PRI是最终的PRI


这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行


所以,调整进程优先级,在Linux下,就是调整进程nice值


nice其取值范围是-20至19,一共40个级别。


需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。


可以理解nice值是进程优先级的修正修正数据


我们可以通过top命令,进入top后按“r”–>输入进程PID–>输入nice值  的方式,来将已有的nice值进行修改。


我们这里再补充一下其他的概念:


竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级

独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰

并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行

并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发


再说一点就是,每一个进程在CPU上运行的时候,都会有一个时间片。当时间片到的时候,进程就会从CPU上被强行扒下来。


环境变量

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数


就比如说,在Linux系统中,我们知道,ls、touch等指令,实际上也是一个个可执行程序。但是当我们输入ls指令,touch指令啥的,为啥可以不带路径?


这就和环境变量有关了。


环境变量中有一个PATH项:

而我们当需要执行ls  touch 等指令的时候,会优先去这些路径下寻找。

如图:


也就是说,这些指令早已经被存储了起来,即存储到了这些路径下的文件夹下。并且这些路径早已经被当成环境变量,它可以认为是整个操作系统调用指令的“全局变量”。、


我们输入env,可以看到我们所有的环境变量。


那这么说,如果我将我的可执行程序添加到环境变量里,是不是就可以不带路径直接执行了呢?


确实如此。


不过一般不建议这么做,因为会污染原有的环境变量。


那么我们最起码要说说如何创建、删除环境变量的吧


和环境变量相关的命令

1. echo: 显示某个环境变量值

2. export: 设置一个新的环境变量

3. env: 显示所有环境变量

4. unset: 清除环境变量

5. set: 显示本地定义的shell变量和环境变量


就直接在命令行中去试试就可以了。我在这里就不试了,感兴趣的读者可以自行去尝试一下。


环境变量的组织方式

image.png


就这么一张表,相信能看懂。意思其实很简单,其就是用指针数组的形式来存储的。


这是我们在命令行中所说的一些关于环境变量的内容。


那要是在程序中呢?


我们在这里,先说一个知识点,叫做main函数的参数。


main函数的三个参数

可能很多读者会看到书上曾经写过main函数的参数,实际上,main函数有三个参数,只不过我们平时不写,系统已经帮我们默认了。


我们借此机会,将其讲解一下:


这三个参数是这样的:

int main(int argc, char* argv[], char* env[])


其中,前两个是命令行参数,最后的那个是环境变量参数。


我们来通过例子的方式讲解:


我们创建一个文件myfile.c,这是代码:

1 #include
    2 int main(int argc,char* argv[],char* env[])
    3 {
    4   int i = 0;
    5   for(;i
    6   {
    7     printf("%s\n",argv[i]);                                                                                                                                                                
    8   }
    9   return 0;
   10 }


然后我们正常编译,来看:


如果我们这样去运行:


得到的就是这样。

如果我们这样去运行:

得到的结果是这样:

 

如果我们这样运行,得到的结果是这样:

以此类推,在此就不过多举例了,我们已经能够发现这其中的规律了:


就是在命令行中,我们怎样去执行的,那么我们就会看到怎样的结果。


也就是说,我们加了多少个-x选项,这里的argc就是其个数。


而argv[i]存储的正是每一个参数的-x选项的内容。


如果没有带选项,那么默认就是我们的文件名。


这个用法实际上让人很容易联想到strtok函数(这个我们后面再说)


那有什么运用场景呢?


可以想一想,我们的ls后面的那些选项,内部的逻辑是不是会存在着if(argv[i] == '-a')这样的逻辑呢?


好啦,前两个变量说完了,它们都是命令行参数,第一个是和程序执行时后面带着的 -x 的个数有关系(x可以为a,b,c...),而第二个参数正是存储每一个 -x 参数的(每一个-x都是一个字符串,它们的首元素地址存储到指针数组argv中)。


那后面的变量?


就是环境变量参数?


是的!


我们来看这样一段代码:

我们可以看到,其将我们的环境变量全部打印了出来。

这和我们刚刚在命令行中直接用env打印出来的是一样的。有兴趣的小伙伴可以一试。


同样,也可以用第三方变量environ获取、


就像这样:


这样后运行的结果和刚刚的是一模一样的。这里的environ可以理解为存储着指针数组的二级指针。


通过系统调用设置环境变量


我们同样还可以通过系统调用设置环境变量

通过这样,我们就可以获得环境变量PATH。


运行后和我们刚刚直接 echo $PATH 的结果是一样的


另外,我们再强调一下,环境变量是具有全局性的,也就是说,环境变量也是可以被子进程继承下去的。




目录
相关文章
|
16天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
57 3
|
16天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
48 2
|
1天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
26天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
97 4
linux进程管理万字详解!!!
|
10天前
|
Ubuntu Linux 网络安全
linux系统ubuntu中在命令行中打开图形界面的文件夹
在Ubuntu系统中,通过命令行打开图形界面的文件夹是一个高效且实用的操作。无论是使用Nautilus、Dolphin还是Thunar,都可以根据具体桌面环境选择合适的文件管理器。通过上述命令和方法,可以简化日常工作,提高效率。同时,解决权限问题和图形界面问题也能确保操作的顺利进行。掌握这些技巧,可以使Linux操作更加便捷和灵活。
15 3
|
16天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
58 8
|
13天前
|
网络协议 Linux 虚拟化
如何在 Linux 系统中查看进程的详细信息?
如何在 Linux 系统中查看进程的详细信息?
35 1
|
13天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
16天前
|
安全 网络协议 Linux
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。通过掌握 ping 命令,读者可以轻松测试网络连通性、诊断网络问题并提升网络管理能力。
53 3
|
19天前
|
安全 Linux 数据安全/隐私保护
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。本文介绍了使用 `ls -l` 和 `stat` 命令查找文件所有者的基本方法,以及通过文件路径、通配符和结合其他命令的高级技巧。还提供了实际案例分析和注意事项,帮助读者更好地掌握这一操作。
37 6