总言
进程基本概念:冯诺依曼体系结构、操作系统、进程、进程状态、进程优先级、环境变量。
1、冯诺依曼体系结构
1.1、概述
1.2、是什么
1)、冯诺依曼体系结构中,各个组成部分具体指什么?
①、问:存储器是指的是什么?是内存还是存储器?
回答:内存
②、输入设备:键盘、显示器、音响、磁盘、网卡等
③、输出设备:显示器、音响、磁盘、网卡等
ps:有些设备二者兼具。
④、CPU中央处理器
主要组件:运算器、控制器
运算器:算术运算、逻辑运算
控制器:CPU是可以响应外部事件的(协调外部就绪事件,比如:数据拷贝到内存)
1.3、为什么
1.3.1、冯诺依曼体系为什么要这样设计?
问题解释:输入设备用于产生数据,输出设备用于保存或显示数据,为什么它们不直接与中央处理器做交互,而要在二者中间横跨一个存储器?
两个基本认识:
1、计算机中数据处理速度(数量级差距):CPU&&寄存器>>内存>>磁盘/SSD>>光盘>>磁带
2、木头原理:一只木桶盛水的多少,并不取决于桶壁上最高的那块木块,而恰恰取决于桶壁上最短的那块。
原因整合:
1、若按照直接交互的方法设计,在计算机这个体系中,外设与CPU直接交互,后者处理信息的速度将被拖慢。
2、故在二者间加入一个存储器,其速度介于两者之间。虽然仍旧是木头原理,但我们可以在软件层面做文章。
3、考虑到CPU成本问题,不选择寄存器而选择以存储器,相当于以较低的价格提高较大的效率。(凡是被广泛传播的产品,一定是价格便宜,质量良好的。)
结论:
1、站在数据的角度,CPU不直接与外设打交道,其读取数据都是从内存中读取。
2、同理,外设只和内存打交道,外设的数据需要预先加载到内存中,才被CPU读取。
IO:input、output
相关例子:学习工具章时,我们写的进度条,没有立马被刷新到显示器上,是因为其还在内存中。
2)、问题:如何理解"程序要运行,必须先被加载到内存中"这句话?为什么呢?
理解:程序属于文件,文件存储在磁盘中,根据冯诺依曼体系结构,其要运行(在CPU上跑),则需要经过存储器。
为什么:由冯诺依曼体系结构的特点决定,CPU只和存储期打交道。
3)、问题:从你登录上qq开始和某位朋友聊天开始,数据的流动过程。从你打开窗口,开始给他发消息,到他的到消息之后的数据流动过程。如果是在qq上发送文件呢?
个人理解:皇帝、宰相、各种奏折。
2、操作系统
2.1、概述
2.2、是什么和为什么
1、操作系统:主要作用是管理。对上提供一个良好使用环境(目的),对下管理好软硬件资源(手段),保证系统稳定性。
2.3、怎么办到的
1、system call 系统调用:
Linux是用C语言写的,system call即C语言提供的各种函数,这些函数接口即系统调用,注意,它们不是C语言的库函数。
3、进程
3.1、进程是什么
1)、Windows下查看任务管理器:可看到进程。
需要知道的信息:
1、当我们启动一个软件时,就是启动了一个进程。
2、在Linux下,运行一条指令,或者运行./ XXX程序,就是在系统层面创建了一个进程。
需要纠正的认识:
1、当一个程序被加载到内存中时,就不能称之为程序,而应该叫做进程。
2、站在操作系统的角度,其只能对进程做处理,因此,我们需要将磁盘内部的程序(本质即文件)转化为进程后(程序运行起来),才能被操作系统所管理。
2)、对进程的理解推进:数据结构化认识 & 为什么需要PCB?
3.2、PCB
3.1.3、PCB是什么?内部其属性都有哪些?
3.3、如何查看进程:相关指令操作介绍
3.3.1、准备工作
1)、预先准备:编写一个程序,使其运行起来成为进程
相关代码如下:死循环是为了后续方便观察进程。
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main(void)
5 {
6 while(1)
7 {
8 printf("hello vim!\n");
9 sleep(1);
10 }
11 }
3.3.2、方法一:ps axj
1、使用ps
,一般只显示当前终端下的一些进程,若想查看所有进程,则需要在后面跟上axj
。
ps
ps axj
2、为了方便查找需要的进程,我们可用和之前学习的指令结合起来使用
ps axj | grep "proc"//grep后跟查找的相关内容
ps axj | head -1 && ps axj | grep proc
3.3.3、方法二:top
1、top
使用时类似于Windows下的资源管理器,是实时动态的过程,一般用来查看大型进程数据。按Q
可退出top。
3.3.4、方法三:ls /proc(cwd、exe路径介绍)
1)、ls /proc 目录介绍
[wj@VM-4-3-centos t1104]$ ls /proc
[wj@VM-4-3-centos t1104]$ ls /proc -l | grep '20742'
2)、exe、cwd相关说明
既然进程是一个目录文件,可使用ls -al
打开目录详细查看。
[wj@VM-4-3-centos t1104]$ ls /proc/20742(进程ID) -al
3)、/proc目录动态性说明
/proc
该进程目录是实时动态的。当我们结束用于演示的proc进程时,进程目录中该进程ID就不存在了。
3.4、获取进程标示符:第一个系统调用
3.4.1、getpid 、kill -9
1)、相关函数介绍
说明: 获取进程标识符的相关函数,getpid
注意:
①该函数所在头文件:<sys/typese/h>
、<unistd.h>
,是系统库函数(非C库)
②pit_t
:实际上是无符号整数
2)、实际使用举例:获取当前进程getpid()、杀掉进程kill -9
实际中使用该函数获取进程:pid_t pid=getpid();
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4
5 int main(void)
6 {
7 while(1)
8 {
9 pid_t pid=getpid();
10 printf("hello pid:%d",pid);
11 sleep(1);
12 }
13 }
进程ID有何用? 比如此处,知道相应进程的ID值,我们就可以使用发送信号的方式,将进程关闭:kill -9 pid
,这里对应的是九号信号。
当然,这里只是对进程ID作用的举例,其相关说明后续慢慢学习。
3.4.2、getppid、bash
1)、获取父进程PPID,getppid
根据上述,我们也可以使用同样的方式获取到父进程ID值。演示如下:
2)、父进程的父进程
问题引入:既然进程有其对应父进程,那么它的父进程是谁?
衍生问题:bash可以使用kill -9杀掉吗?
回答:可以,只是删除后当前终端窗口命令行混乱,但不影响另一个终端窗口命令行(因为其是另一个bash)
衍生:操作系统也是一个死循环。
3.5、fork:通过代码创建一个子进程
3.5.1、fork基本介绍与返回值演示
1)、fork初识,fork返回值
说明: fork也是一个系统调用接口,其作用是帮助我们创建子进程。
特点一:fork具有两个返回值:
1、失败时,返回-1;
2、成功时,给父进程返回子进程的ID,给子进程返回0.
2)、关于使用fork之后存在两个返回值的理解实例演示
实例演示一:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(void)
{
printf("I am parent process!\n");
pid_t ret=fork();
printf("you can see me.\n");//注意观察该语句输出结果
return 0;
}
实例演示2:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4
5 int main(void)
6 {
7 printf("I am parent process, pid:%d\n",getpid());
8
9 pid_t ret=fork();
10 //1、确认fork之后是否存在两进程
11 //2、确认该两个进程是否为父子关系
12 printf("ret:%d; pid:%d ; ppid:%d\n",ret,getpid(),getppid());
13
14 sleep(1);
15 return 0;
16 }
3.5.2、fork基本使用
1)、fork一般如何使用?
1、fork之后,代码是父子进程共享的。
2、我们并不需要父子进程同时做一件事,所以创建父子进程,一个需求是考虑让它们各自执行自己的任务。
3、所以问题就转换为,如何使用fork创建父子进程,从而达到分工目的?
相关演示如下:可以根据fork的返回值,使用条件语句,让父子进程分别执行不同行为操作。
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main(void)
5 {
6 pid_t id=fork();
7 if(id<0)//若创建进程失败
8 {
9 perror("fork");
10 return 1;
11 }//以下为成功创建进程
12 else if(id==0)
13 {
14 //children process:
15 }
16 else
17 {
18 //parent process:
19 }
20
21 return 0;
22 }
说明:
实际运用举例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(void)
{
pid_t id=fork();
if(id<0)//若创建进程失败
{
perror("fork");
return 1;
}//以下为成功创建进程
else if(id==0)
{
//children process:
while(1)
{
printf("I am child , pid:%d ; ppid:%d \n",getpid(),getppid());
sleep(1);
}
}
else
{
//parent process:
while(1)
{
printf("I am parent , pid:%d ; ppid:%d \n",getpid(),getppid());
sleep(1);
}
}
return 0;
}
2)、对上述代码的执行思考
问题1:进程创建成功后(即后续else if和else中的内容),有没有可能是同时执行的?
(即按照以往我们对C语言的学习,if……else分支语句,一般只执行其中一个选项内容)
问题2:仍旧是上述问题,有没有可能,两个while循环同时执行?
以下为验证演示:
1、首先需要一个脚本方便观察:
while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep;sleep 1; done
//使用下面这个是相对优化了以下便于观察。
while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep; sleep 1; echo "##################################################################";done
2、然后,我们来执行上述代码:
3、从执行结果可知:
①、父子进程同时运行,说明条件语句中的两个条件都在执行,也说明两个while循环在同时运行。
②、为什么呢?
因为父子进程id不同。
③延伸问题:上述情况是否说明,一份C语言代码,其中同一个变量会有不同的值?
回答:需要学习到进程地址空间才能理解。
3.5.3、关于fork两个返回值解释
1)、从感性的角度理解为什么fork给子进程返回0,给父进程返回进程的pid?
感性理解:
1、即一个父进程可以有多个子进程,但一个子进程只能有一个父进程。
2、父进程为了方便管理子进程,需要得到能够识别每个子进程的标识符,此处即子进程id。
3、而子进程只需要知道自己是否被建立即可,其父进程可通过getppid得到。
2)、fork创建子进程,为什么其会得到两个返回值?
1、阶段理解一:
问题:创建进程的时候,OS要做什么?
回答:本质是OS操作系统中多了一个进程。
2、阶段理解二:
①前提认知: 操作系统和CPU运行某个进程,本质是从PCB形成的队列中,挑选一个PCB来执行其对应的进程的代码。
进程调度:即在PCB(Linux下是task_struct)的队列中选择一个进程的过程。
PS:只要想到进程,优先要想到进程对应的控制块。
②问题: 使用fork时,当该函数准备return了,我们的核心代码执行完了吗?
回答:已经执行完成了。return并没有参与计算,而是准备将得到的结果返回到上层,既然已经得到了结果,那么说明中间计算过程已经完成,结果已存在,即意味着核心代码已经执行完成。
3、阶段理解三:
问题:基于上图,我们可知,fork内部return执行两次。但是,这并不意味着pid _t ret=fork();
中,ret将保存两次return值。
即:同一个ret,怎么能得到两个值?
回答:涉及后续进程地址内容。return返回值,本质就是在对id进行写入操作,发生了写时拷贝,故父子进程在物理内存中有各自的变量空间,只不过在用户上,我们使用了同一个变量(虚拟地址)来标时它。
3)、父子进程被创建出来,谁先被运行?
关于该问题的来源简述: 在fork为什么能得到两个返回值中,我们分析推演得到PCB(进程控制块)是以队列的形式被CPU运行。队列具有先进先出的特性。我们在父进程中创建子进程,按理说父进程已经执行了,因此下一次该执行的是子进程。但更具我们之前用于演示的printf代码可得出,父进程先运行。
这说明实际情况和我们推测的先父后子不一致,因此才有了这个父子进程执行顺序的问题。
回答:不一定。谁先进程由操作系统的调度器来决定。
4、Linux操作系统进程状态
4.1、是什么
新建: 即进程被创建(此时{PCB还没有进入队列中)
运行: PCB(task_struct)在运行队列中排队,就叫做运行态(即进程不一定正在被CPU执行)
阻塞: 等待非CPU资源就绪,进程所处的状态叫做阻塞状态(系统中存在各种资源,CPU、网卡、磁盘、显卡等。因此系统中不止存在一种队列,在CPU中的队列称之为运行队列。)
挂起: 当内存不足时,操作系统(OS)通过适当置换进程的代码和数据到磁盘中(SWAP分区),此时进程的状态叫做挂起。(当挂起状态要被运行,首先第一件事是要将这些数据调回内存中,再将状态调制为运行态。)
1、阻塞状态的一个例子:
int main()
{
int a=0;
scanf("%d",&a);
//……
return 0;
}
场景: 对上述这段代码,当我们运行这个程序,将其称为进程进入CPU中,scanf
函数需要从键盘中输入一个值。假如我们一直不输入该值,会出现什么现象?
结果:从进程角度,该进程就需要等待键盘数据就绪。从CPU角度,那么多个进程需要被执行,CPU不可能因为这一个进程而放弃其它进程,此时该进程会进入阻塞状态,直到我们输入的值(即键盘资源就绪完毕),那么CPU才会考虑是否执行该进程。
延伸情景: 若我们运行一个程序,它陷入卡机状态时,可以从两方面考虑
其一:CPU被很多进程资源占用,导致当前这个进程很长时间得不到调用。
其二:当前进程在等待某种资源就绪,但很长时间都没准备好。
2、挂起阻塞:
一个并不冲突的状态。只不过是进程在阻塞时顺便遇到了CPU资源不足的时候,反正都是等,与其占用着CPU资源等待,那么干脆就将其数据挪动到磁盘中吧。
事实上数据换入换出遵循冯诺依曼体系结构,其也是一项效率工程。
4.2、Linux下的进程状态
4.2.1、总体介绍
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。下面的状态在kernel源代码里定义:
/*
* 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 */
};
4.2.2、运行状态( R )、睡眠状态( S )
现象演示一: 运行while死循环,没打印printf内容,程序状态为R,打印pirntf内容,程序状态为S。
解释: printf打印到显示器上,根据冯诺依曼理论,显示器为外设,进程访问外设,外设不一定立马准备好,没有准备好时进程就处于阻塞状态。故该进程在阻塞队列中的时间占比比在运行队列中的时间占比要大,而CPU调度速度又很快,因此该进程绝大部分时间处于S中(而把prinf去掉,其状态就在R)。
4.2.3、前台进程后台进程、暂停状态(T)、休眠状态(S、D)
1)、前台进程和后台进程
问题: 在上述演示中,我们看到状态后面会跟随一个+
号,这是什么呢?
2)、可中断睡眠状态(S)和不可中断睡眠状态(D)、暂停状态(T)
暂停(T)与休眠(S)
磁盘睡眠状态(D): 源于关于这锅谁背的问题讨论?(感性化认识)
分析场景:
1、操作系统中有一个进程A,其将访问磁盘,在其中存入一些数据。
2、此时磁盘接受到任务,正在缓慢执行进程A交予的数据存储工作。最后需要将任务结果(成功/失败)反馈给进程A。
3、由于磁盘读写速度低于CPU与内存,此处进程A需要等待磁盘读写结果。
进程A在等待磁盘资源就绪,该过程就是阻塞,即S状态。
4、在上述条件下,OS正巧处于一个内存资源极度紧张的状态(比如:OS收到了很多任务,导致内存紧张,已经在做交换分区等各种有益于空出内存资源的尝试)
5、因此,当服务器压力过大时,OS通过一定手段杀掉一些进程来起到节约空间的作用。
6、在上述条件下,对进程A交予的任务此时磁盘得到了结果,比如写入失败,它将把结果反馈给进程A,但此时进程A已经被操作系统删除。
7、磁盘找不到反馈信息的上层者,而自己又有其它新的任务,此时该份数据就会被丢弃。即数据丢失。
上述这种情况下,最终导致用户数据丢失,这锅谁来背?
OS:这能怪我?!我的编写者设计我的时候说明,一旦资源不足,我可以通过放弃一些进程的方式来让我正常运转。若挂掉的不是进程A那就是我,如果我挂掉了,别说这一项数据,整个操作系统数据都没了。
进程A:人在家中坐,祸从天上来。我明明睡得好好的在等待磁盘反馈结果,然而OS二话不说就把我给干掉了,我这可真冤。
磁盘:莫问,问就是个跑腿的。我收到任务战战兢兢在执行,出错了需要将结果反馈回去,然而连进程半点影子也找不到。
于是,就产生了D状态,让进程A处于堵塞状态,但OS无法将其杀死。
实际解决场景:关机重启或拔电源。
4.2.4、终止状态(X)、僵尸状态(Z)、孤儿进程
1)、终止状态(X)
为什么要存在这个状态?
操作系统面对的是多个进程,当前进程运行结束时不一定能被操作系统立马回收。比如有多个进程同时需要退出,那么总得一个一个来;又比如操作系统忙着其它任务,先把这个要回收的进程暂时放一放。
2)、僵尸状态(Z)
是什么和为什么
僵尸状态相关演示
代码如下:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();//创建一个子进程
if(id<0)
{
perror("fork");
return 1;
}
else if(id==0)
{
while(1) //让子进程运行,3秒后退出
{
printf("I am child, pid:%d ,ppid:%d \n",getpid(),getppid());
sleep(3);
break;
}
exit(0);
}
else
{
while(1) //此时父进程仍旧运行
{
printf("I am parent, pid:%d ,ppid:%d \n",getpid(),getppid());
sleep(1);
}
}
return 0;
}
僵尸进程的危害
僵尸进程会带来一系列的危害,主要包括内存资源的浪费和潜在的系统问题。 以下是关于僵尸进程危害的详细解释:
1、内存资源浪费: 僵尸进程虽然不再执行任何代码,但它们仍然在进程表中占用一个条目。每个进程表项都需要一定的内存来存储进程的相关信息,如进程ID、父进程ID、进程状态等。如果父进程不及时回收其已终止的子进程,那么这些僵尸进程就会一直占据进程表中的位置,导致内存资源的浪费。 特别是在高并发环境下,大量僵尸进程的存在会显著加剧内存资源的消耗。
2、系统性能下降: 由于僵尸进程占用了进程表资源,当进程表满员时,系统可能无法再创建新的进程。这可能导致一些正常的进程创建请求被拒绝,从而影响系统的正常运行。此外,系统还需要维护这些僵尸进程的状态信息,这也会增加系统的开销,降低系统的性能。
3、安全隐患: 恶意软件可能会利用僵尸进程的特性来攻击系统。例如,攻击者可以创建大量的僵尸进程来消耗系统资源,导致拒绝服务攻击(DoS)。此外,如果僵尸进程被用于隐藏恶意活动或传播病毒等恶意代码,那么它们还可能对系统的安全性构成威胁。
如何避免?(后续博文会介绍:进程等待。)
父进程应该负责回收其已终止的子进程。这通常通过调用如wait()
或waitpid()
等系统调用来实现,这些调用会阻塞父进程直到有子进程退出,并返回子进程的退出状态。这样,父进程就可以及时获取子进程的退出信息,并释放子进程所占用的资源,从而避免僵尸进程的产生。
3)、孤儿进程
是什么
1、父进程先退出,子进程就称之为“孤儿进程”
2、孤儿进程被1号init进程领养
实列演示 :
#include<stdio.h>
#include<unistd.h>
int main(void)
{
pid_t id=fork();
if(id<0)
{
perror("fork");
return 1;
}
else if(id==0)
{
while(1)//子进程一直循环
{
printf("hello child\n");
sleep(1);
}
}
else
{
int cen=5;
while(cen)//父进程循环5次退出
{
printf("I am father:%d\n",cen--);
sleep(1);
}
}
return 0;
}
现象:
1、孤儿进程会被领养,即图中的1号进程 init
,系统本身。(每当出现一个孤儿进程的时候,内核就会把孤儿进程的父进程设置为init进程,而init进程会循环地等待(wait())它的已经退出的子进程,从而回收和管理这些子进程的资源。)
问题:为什么孤儿进程要被领养?
1、未来子进程退出时,父进程早已不在,因此需要领养子进程,以便未来对其进行回收。(为了避免孤儿进程退出时无法释放所占用的资源而僵死,进程号为1的init进程将会接受这些孤儿进程,这一过程也被称为“收养”。)
补充:
1、对于孤儿进程,直接CTRL+C
是无法对其终止的(由图可看出其从前台运行转变为了后台运行)
2、此时可以使用kill指令(即使孤儿进程的代码在运行,仍旧可以通过输入正确的kill指令来杀死该孤儿进程)
5、进程优先级
5.1、是什么和为什么
1、优先级是一种评判标准,既然设立了这个标准,说明是有裁判来对它做出评判。
2、计算机中用于评判优先级的是调度器,优先级是调度器对进程资源分配的衡量指标。
5.2、怎么做(查看与修改)
一个基本判断公式:
优先级=老的优先级+nice值
查看进程优先级
ps -l :显示当前Xshell对话框下的进程信息
ps -al:显示所有进程信息(以列表形式)
修改优先级:方法有很多,此处介绍的是使用任务管理器修改进程优先级
5.3、一些概念补充:并行、并发
1)、总述
2)、对并发的理解:
1、CPU在运行进程时,并不是将一个进程跑完,才运行下一个进程。(若是这样你只需要设计一个陷入死循环的程序让CPU跑,就可以搞垮系统。
2、CPU运行进程时设置有时间片。
3、CPU不是根据时间片死板的执行进程,其支持抢占与出让,取决于进程优先级。
4、并发状态下的多个CPU,自己内部也是采用并行。
场景举例:就比如电脑上多个进程能被同时运行,这就是CPU并行执行这些进程。
3)、切换
1、之前我们说,进程要被运行,需要从内存中加载到CPU中,具体位置为CPU中的寄存器上。
2、如果进程A正在被运行,cpu内的寄存器里面一定保存的是进程A的临时数据!寄存器中的临时数据,叫做A的上下文。
提问:上下文数据可以被丢弃吗?
回答:绝对不可以!
引申:当进程A暂时被切下来的时候,需要进程A顺便把自己的上下文数据带走。
目的:将上下文数据带走暂时保存,是为了下次回到CPU的时候,数据能够恢复上去,这样进程就能按照之前的逻辑继续向后运行,就如同没有被中断过一样。
CPU的寄存器是一份,但上下文可以有多份,分别对应不同的进程。
6、环境变量
6.1、问题引入与环境变量配置
0)、一个前提认知
1、Linux下操作系统的指令能直接使用,我们创建的程序要运行需要带上路径。
2、对于操作系统的指令,我们也可以带上对应的路径去运行它。(此处提醒:Linux下的指令实际上就相当于一个程序,若忘记了相关内容请回顾之前写的博客)
[wj@VM-4-3-centos t1108]$ ls
Makefile proc.c proc.out
[wj@VM-4-3-centos t1108]$ which ls
alias ls='ls --color=auto'
/usr/bin/ls
[wj@VM-4-3-centos t1108]$ /usr/bin/ls
Makefile proc.c proc.out
1)、为什么操作系统的指令能够直接使用,而我们创建的proc程序需要加上./proc(即加上对应路径才能运行)?如何让我们自己的程序能像系统程序一样直接使用?
1、Linux要运行一个程序,就需要找到它对应的路径。因此我们自己写的程序,如果要运行它,则需要带上相应的路径。
2、对于系统指令,操作系统默认能找到它对应的路径。因为这些指令的路径都在环境变量中。
图示为查看环境变量的方法: echo $PATH
1、环境变量中的路径是以":
"(冒号)作为分割符的。
2、当使用某个指令时,操作系统逐个寻找环境变量中的路径,查看有没有该指令,找到后执行它。
3、即环境变量维护了大量的路径,这些路径是我们使用指令时的搜索路径。
4、也就能得出,我们自己所写的程序要带上路径,是因为它不在环境变量所维护的众多路径中。
2)、能否做到让自己的可执行程序像操作系统中的指令一样不需要路径也能运行?
法一:将我们的指令写入$PATH
环境变量中存在的任意一个路径中(但这样会污染别人的命令池)。
法二:将我们当前程序的路径放入 $PATH
环境变量中。
以下为方法二的使用介绍:
export PATH=$PATH:XXX/XXX
,其中XXX/XXX即我们自己的程序所在路径。
ps:这种在命令行中修改环境变量的方式,只针对本次登录有效。退出重新登录时则恢复到默认配置文件设置的环境变量。
以下为windows环境变量展示:
6.2、常见环境变量
1)、需要知道并非只有上述描述的PATH一个环境变量
[wj@VM-4-3-centos T0611]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/wj/.local/bin:/home/wj/bin
[wj@VM-4-3-centos T0611]$ echo $HOME
/home/wj
[wj@VM-4-3-centos T0611]$ echo $SHELL
/bin/bash
2)、如何查看Linux系统下的所有环境变量?
env
: 显示所有环境变量
如下,显示的每一行都是环境变量,这里也有我们之前提到的SHELL=/bin/bash
、HOME=/home/wj
,等。
[wj@VM-4-3-centos t1108]$ env
XDG_SESSION_ID=325284
HOSTNAME=VM-4-3-centos
TERM=xterm
SHELL=/bin/bash
HISTSIZE=3000
SSH_CLIENT=36.161.119.151 5480 22
OLDPWD=/home/wj/one.-studybylinux/study2022/T2211
SSH_TTY=/dev/pts/0
USER=wj
LD_LIBRARY_PATH=:/home/wj/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/wj
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/wj/.local/bin:/home/wj/bin:/home/wj/one.-studybylinux/study2022/T2211/t1108
PWD=/home/wj/one.-studybylinux/study2022/T2211/t1108
TST_HACK_BASH_SESSION_ID=12817850297754
LANG=en_US.utf8
SHLVL=1
HOME=/home/wj
LOGNAME=wj
SSH_CONNECTION=36.161.119.151 5480 10.0.4.3 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
PROMPT_COMMAND=history -a; printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
XDG_RUNTIME_DIR=/run/user/1001
HISTTIMEFORMAT=%F %T
_=/usr/bin/env
[wj@VM-4-3-centos t1108]$
6.3、获取环境变量
1)、环境变量的组织方式
2)、通过命令行参数获取环境变量
通过本次演示需要知道的内容:
我们可以通过main函数的参数获取环境变量。
输入代码:
int main(int argc, char*argv[], char *env[])
{
printf("begin----------------------------------\n");
int i=0;
for(i=0; env[i]; i++)
{
printf("env[%d]:%s\n",i,env[i]);
}
printf("end-----------------------------------\n");
return 0;
}
简单解释:
运行结果:
[wj@VM-4-3-centos t1108]$ ./proc.out
begin----------------------------------
env[0]:XDG_SESSION_ID=325284
env[1]:HOSTNAME=VM-4-3-centos
env[2]:TERM=xterm
env[3]:SHELL=/bin/bash
env[4]:HISTSIZE=3000
env[5]:SSH_CLIENT=36.161.119.151 5480 22
env[6]:OLDPWD=/home/wj/one.-studybylinux/study2022/T2211
env[7]:SSH_TTY=/dev/pts/0
env[8]:USER=wj
env[9]:LD_LIBRARY_PATH=:/home/wj/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
env[10]:LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
env[11]:MAIL=/var/spool/mail/wj
env[12]:PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/wj/.local/bin:/home/wj/bin:/home/wj/one.-studybylinux/study2022/T2211/t1108
env[13]:PWD=/home/wj/one.-studybylinux/study2022/T2211/t1108
env[14]:TST_HACK_BASH_SESSION_ID=12817850297754
env[15]:LANG=en_US.utf8
env[16]:SHLVL=1
env[17]:HOME=/home/wj
env[18]:LOGNAME=wj
env[19]:SSH_CONNECTION=36.161.119.151 5480 10.0.4.3 22
env[20]:LESSOPEN=||/usr/bin/lesspipe.sh %s
env[21]:PROMPT_COMMAND=history -a; printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
env[22]:XDG_RUNTIME_DIR=/run/user/1001
env[23]:HISTTIMEFORMAT=%F %T
env[24]:_=./proc.out
end-----------------------------------
[wj@VM-4-3-centos t1108]$
3)、通过第三方变量获取环境变量
environ
第三方变量:通过它能够获取环境变量,使用方式和main函数相差无几。
int main(int argc, char*argv[], char *env[])
{
printf("begin----------------------------------\n");
int i=0;
extren char** environ;
for(i=0; environ[i]; i++)
{
printf("env[%d]:%s\n",i,environ[i]);
}
printf("end-----------------------------------\n");
return 0;
}
4)、最常用的获取环境变量的方法
上述两种获取环境变量的方法一般都不常使用,原因是在这两种方法下,所得到的环境变量被当作字符串使用,我们需要哪个环境变量还得自己去分析。
getenv
使用演示:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc, char*argv[], char *env[])
{
printf("begin----------------------------------\n");
printf("%s\n",getenv("PATH"));
printf("end-----------------------------------\n");
return 0;
}
[wj@VM-4-3-centos t1108]$ ./proc.out
begin----------------------------------
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/wj/.local/bin:/home/wj/bin:/home/wj/o
end-----------------------------------
5)、我们能获取环境变量,这些环境变量是谁给我们的?
引入
1、我们所得到的环境变量,是从父进程那里继承来的。
2、而父进程的环境变量是从它的父进程那继承来的。
3、如此倒推,最终来到bash。
问题一:如何证明?
int main(int argc, char*argv[], char *env[])
{
printf("begin----------------------------------\n");
printf("%s\n",getenv("MYENV"));//定义一个我们自己设置的环境变量
printf("end-----------------------------------\n");
return 0;
}
说明:exprot
能够导出环境变量,在上述中,我们使用export MYENV="hello world"
设置了一个环境变量,此时MYENV
该环境变量在Bash
父进程里。 proc.out
实际上是一个子进程,运行./proc.out 程序,它能从父进程Bash处继承该环境变量。
两个结论:
1、子进程的环境变量是从父进程处得来的。
2、默认所有的环境变量都会被子进程继承。
问题二:每次登录时,Bash的环境变量从哪来?
回答:Bash的环境变量从操作系统来。 因为这个原因,子进程的子进程也能继承相应的环境变量(如此形成的多叉树上都继承了其父继承的环境变量,而它们又源于共同的根),由此可得,环境变量具有全局属性。
延伸:普通变量和环境变量的区别
在不使用export导出的情况下,也能在bash命令行操作中定义变量,但其不属于环境变量。
对于这类变量,可通过set
指令,将Bash中的环境变量和私有变量全显示出来,就能查找到它。
getenv没有获得我们设置的bit普通变量:
结论:在命令行中能定义两种变量,一种是普通变量(局部变量),一种是环境变量,后者具有全局属性
6)、补充知识:main函数中的argc、argv
argc、argv
是什么?
1、argc、argv是命令行参数
2、argc决定命令行参数的个数,argv和env类似,是指针数组,用于存储输入的命令选项
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc, char*argv[], char *env[])
{
printf("begin----------------------------------\n");
int i=0;
for(i=0; i<argc; i++)
{
printf("argv[%d]:%s\n",i,argv[i]);
}
printf("end-----------------------------------\n");
return 0;
}
argc、argv
是由谁传递的?
命令行参数可以让我们对于同一个程序,通过选项来选择该程序的不同子功能。
例如:ls -a、ls -l -a 等。我们输入的这些选项就是命令行参数,因选项输入的不同我们得以执行不同命令。
当我们做命令行调用时,这些选项是由父进程给予的。
其它:
ls -a -l
如果我们带了选项,那么ls即argv[0]。
ls
如果我们不带选项,那么ls即argc至少是1。