【Linux】进程概念解释和linux环境变量
本篇博客记录了Linux下一些概念的解释,以及环境变量的相关操作
所用系统:CentOS 7.6
1.概念解释
1.1 进程竞争性
系统进程的数目较多。而CPU资源等其他资源不够用,所以进程之间存在竞争性,也就出现了优先级,这在上篇博客中有过介绍
1.2 进程独立性
进程运行具有独立性,不会因为某个进程出错,而影响其他进程的运行
我们知道,一个进程是内核结构task_truck+代码和数据
组成的。而linux系统是通过进程地址空间
方式来保证进程的独立性,那是下篇博客会讲到的内容
1.3 并行
并行:多个进程在多个CPU下分割,同时运行
我们一般的电脑都是只有1个cpu,那是怎么做到多个进程运行的?
注意:多个进程都在系统中运行≠多个进程在系统中同时运行。要想知道这是怎么做到的,需要了解并发的概念
1.4 并发
大部分操作系统都是分时
的,操作系统会给每一个进程赋予一个时间片,这样在一个调度周期中,可以调用到每一个需要运行的进程。
这样,在一个时间段内,多个进程会通过交叉运行的方式,让每一个进程的代码,在这段时间内都能得到运行
比如每一个进程运行10ms,假设有10个进程需要运行,那么在1s内,这10个进程都会被运行10次。
1s=1000ms
cpu进行多个进程的快速交替运行,以实现我们看到的单cpu运行多个进程的情况
这种情况就叫做并发
1.5 进程优先级管理
操作系统正在运行一个低优先级进程的时候,突然来了一个高优先级数据怎么办?
- 操作系统支持不同优先级进程的存在
- 同一个优先级的进程可以存在多个
- 操作系统是用
队列
来管理进程的,队列只支持支持尾删尾插
这种情况要怎么处理呢?如果给队列头插,那不就破坏了队列的属性了?
实际上,操作系统并不是用单一队列来进行管理的,而是用了一个哈希桶来进行处理
可以理解为哈希桶是一个数组,后面链接了不同优先级的进程
当一个更高优先级的进程需要运行的时候,操作系统只需要改变cpu当前运行队列在哈希桶里面的指向,即可马上完成高低优先级进程之间的切换
操作系统还会管理一个位图,用于标识某一个优先级内是否存在进程
哈希和位图这两个数据结构都会在C++的博客中讲解
除了位图和哈希表之外,操作系统还会管理一个结构体,用于标识目前正在使用的进程哈希桶active
,以及新进程的管理桶old
1 | struct runqueue{ |
当出现新进程的时候,操作系统不会把它直接链接到active
里面,而是放入old
中;
active
内部的进程运行完了之后,只需要swap
一下active/old
指针,即可完成新进程表和运行完的旧表之间的交换!
- 这种调度算法也被称为
大O(1)调度法
1.6 进程间切换
CPU存在寄存器,这些寄存器储存了进程的临时数据
- 寄存器分为可见寄存器(eax/ebx)和不可见寄存器
当进程在运行过程中,会产生各种临时数据
1 | int test(){ |
我们知道,定义在函数test
中的a是一个局部变量,出了作用域就会销毁,那么main函数里面的b是怎么拿到test的返回值的?
这里就用到了寄存器:a的值会先放入寄存器中,销毁了之后,再把寄存器里面的值赋值给b
和内存一样,CPU只有一套寄存器。这一套寄存器在运行不同进程的时候,可以保存不同的临时数据
这时候如果来了一个高优先级进程A,直接覆盖了正在运行的进程B及其寄存器中的数据,就可能导致进程B存在寄存器中的数据丢失,导致B无法继续运行
所以,在进程的task_struct
结构体中,就有一个专门的成员用于保存进程的上下文数据
- 上下文数据:进程在运行中产生的各种临时数据
- 当进程被剥离的时候,需要保存上下文数据
- 当进程恢复运行的时候,需要重新加载上下文数据
关于进程pcb和task_struct的内容可以看我之前的博客 进程概念
2.环境变量
2.1 引入环境变量
当我们运行自己编译的一个可执行文件的时候,需要带上./
指定路径
使用file
命令查看系统的相关指令,你会发现它们和我们自己写的mytest本质上是一样的,都是一个executable
的可执行文件
1 | [muxue@bt-7274:~/git/raspi/code/22-10-04_环境变量]$ file /bin/ls |
那为何运行诸如ls pwd gcc
等等系统命令的时候,不需要在前面带上./
路径来运行呢?
1 | [muxue@bt-7274:~/git/raspi/code/22-10-04_环境变量]$ mytest |
这是因为:指向一个可执行程序,前提是需要找到它!
linux系统只能找到它自己预设好的命令,找不到我们的mytest
在linux命令行中,输入env
即可查看当前系统的环境变量
其中PATH
就是可执行程序存放的路径!系统就是通过环境变量来查找可执行程序的
2.2 添加删除环境变量
别急,我们先来学习一下怎么添加环境变量。实际上,我们的bash命令行里面是可以定义变量的,变量分为两种类型
- 本地变量(局部)
- 环境变量(全局)
直接使用变量名=值
的方式,就可以定义一个本地变量。使用echo
命令可以查看这个本地变量。这时候我们用env | grep 变量名
在环境变量里面查找,会发现当前的环境变量里面没有这个东西
1 | [muxue@bt-7274:~]$ aaaa=1234 |
这时候需要用export
命令,创建一个环境变量
1 | [muxue@bt-7274:~]$ export bbbb=4321 |
或者可以导入当前的本地变量
1 | [muxue@bt-7274:~]$ export aaaa |
删除的时候则使用unset
命令取消环境变量
1 | [muxue@bt-7274:~]$ unset bbbb |
查看环境变量
- echo: 显示某个环境变量值
- export: 设置一个新的环境变量
- env: 显示所有环境变量
- unset: 清除环境变量
- set: 显示本地定义的shell变量和环境变量
1 | echo $环境变量名 #查看环境变量 |
认识一些环境变量
- USER:当前登录的用户
- HOME:当前用户的工作路径
- LANG:当前的语言和编码设定
- PATH:可执行命令的路径
- SHELL:当前使用的命令行是啥
- LOGNAME:当前登录的用户名
- PWD:当前所处路径
- OLDPWD:上一个路径,使用
cd -
跳回上一个路径 - HISTSIZE:系统会记录的历史命令条数
我们可以用history
命令查看之前运行过的命令,这里面保存的正好是3000条,和环境变量HISTSIZE
的设置一致!
1 | [muxue@bt-7274:~]$ env | grep $HISTSIZE |
需要注意的是,系统预载的环境变量都是在配置文件里面的。当前我们对环境变量做的任何操作都只会临时保存。关闭当前的命令行重新开一个,之前设置的环境变量就会消失
1 | [muxue@bt-7274:~]$ cat /etc/bashrc |
系统的环境变量配置文件为/etc/bashrc
,用户的则为工作目录下的.bashrc
以及.bash_profile
查看上个进程退出状态码
在linux下有一个特殊的环境变量?
,这个环境变量存放的是上一个进程的退出码。比如我们进程中return 0
退出,那么查询到的就是0
1 | echo $? |
2.3 环境变量PATH
使用echo $PATH
查看当前系统可执行文件的路径
这里的路径都以:
作为分割,linux查找命令的时候,就会在下面的这些路径里面查找
1 | [muxue@bt-7274:~]$ echo $PATH |
除了直接使用ls
,我们也可以使用路径的方式来调用ls
1 | [muxue@bt-7274:~]$ /usr/bin/ls |
而如果想让系统能找到自己的可执行程序,就可以直接把可执行程序复制到这些路径中!
给PATH中添加可执行文件
1 | [muxue@bt-7274:~/git/raspi/code/22-10-04_环境变量]$ cp mytest ~/bin |
比如现在,我把mytest这个可执行程序复制到了~/bin
也就是/home/muxue/bin
的路径下,此时直接使用mytest就能找到对应的命令了!
除了这种办法以外,我们还可以把当前的路径写入PATH
环境变量中
1 | [muxue@bt-7274:~]$ export PATH=$PATH:/home/muxue/git/raspi/code/test |
这时候直接执行mytest
也成功了!
1 | [muxue@bt-7274:~]$ mytest |
前面提到了我们设置的这个环境变量都是临时的,所以重启了之后,自己设置的这个路径也会消失
1 | [muxue@bt-7274:~]$ echo $PATH |
注意:一般情况下不建议在linux系统路径中安装自己的可执行程序,因为这样会污染系统的命令环境!
3.C/C++获取环境变量
3.1 main函数的参数
之前一直没有了解过这个知识点,C/C++的main
函数是可以带参数的!
1 |
|
有了这两个参数,我们就可以利用它写一个命令行版本的计算器
1 |
|
实现非常简单,其使用方法如下👇
1 | [muxue@bt-7274:~/git/raspi/code/22-10-04_环境变量]$ gcc test.c -o test -std=c99 |
上面这个小程序演示了main
函数的参数的作用。
看到这里,想必你应该不难理解linux
系统的命令是如何使用参数的,诸如ls -l
等等选项,其实都是通过main函数的参数实现的!
通过main
函数的参数,可以让同一个可执行文件依据命令输出不同的结果!
3.2 使用第三个参数获取环境变量
除了上面提到的main函数前两个参数,实际上main函数还可以带第三个参数!
1 | //第一个参数指代命令个数,执行该可执行文件时传入的几个命令 |
因为envs是一个指针数组,所以可以通过判空来终止for循环
在这个数组的最后,可以看到我们刚刚执行的命令也被写入了环境变量
除了上面这个办法,我们还可以用下面两种方式来获取环境变量
environ外部导入环境变量
C语言提供了一个environ来导入环境变量,其作用和main函数第三个参数是一样的
1 | extern char ** environ; |
其输出的结果也是一样的
getenv函数
1 | man getenv |
这个函数就能实现一些骚操作,比如写一个只有我自己可以运行的可执行文件
1 | int main(int arg,char* argv[],char *envs[]) |
通过getenv
函数获取到环境变量中的USER
,判断其与我自己设定的user是否相同。如果不同就拒绝执行,相同才成功执行
1 | [muxue@bt-7274:~/git/raspi/code/22-10-04_环境变量]$ ./test |
可以看到,哪怕是root用户也搞不来这个可执行程序!
4.关于本地变量的说明
在2.2
中提到了本地变量和环境变量的区别
- 本地变量(局部)
- 环境变量(全局)
所谓的本地变量,其实是bash
内部定义的变量。
我们首先需要了解的是,linux下大部分的进程或命令都是以子进程方式运行的,其父进程都是当前打开的bash
由此可知,bash
内部的本地变量,并不会被这些子进程所继承
而环境变量具有全局属性,可以被子进程继承并获取!
那么问题来了,export/echo
也是命令。如果它们也是子进程,那它们是怎么获取到bash内部的本地变量,并将其导入到环境变量中的呢?
nope!实际上,这两个命令都是由bash
自己执行的(调用自己的对应的函数完成功能)我们把这种命令称作内建命令
结语
关于环境变量的基本认识到这里就OVER啦。本博客是我的课堂笔记,难免会有问题,还请各位大佬指出~