目录
🙊 基本概念 🙊
环境变量 ( environment variables ) 一般是指在操作系统中用来指定操作系统运行环境的一些参数,如:我们在编写 C/C++ 代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
💖 PATH 环境变量
思考一个问题,在 Linux 中写代码编译运行为什么会加 ./ ?
事实上我们自己写的程序和系统自带的指令都是程序。只不过系统指令的程序被纳入到 Linux 的基本指令。./ 就是使用相对路径的方式定位可执行程序,当然也可以从根目录进行绝对路径访问。
因为系统指令程序在系统中会存在一个环境变量,这个环境变量可以在系统中的特定路径下搜索对应的命令。系统存在这个环境变量的名字是 PATH。可以使用 echo $PATH 查看环境变量的内容。
PATH 环境变量路径以 : 作为分隔符,可以分出若干个子路径,环境变量在被查找的时候通过从左向右的方向寻找,只要找到就停下。
当我们执行 pwd 或者 ls 等指令的时候,pwd 或 ls 会在特定的环境变量指明的若干路径去找,找到就自动执行。而如果自己编译的可执行程序路径没有在环境变量中,所以执行的时候无法直接执行可执行程序,必须由用户指明路径。
使用 which pwd 命令查看 pwd 命令所在的路径。发现 pwd 的路径在系统环境变量的寻找路径中。此时运行 ls 指令不需要指明路径。
使用 which + 自己写的程序发现程序并没有系统路径中。如果想执行自己程序的时候也不用指明路径,应该怎么做呢?
可以使用以下命令将路径添加到系统的环境变量中:
export PATH=$PATH:/home/potato/project
查看环境变量发现新的路径被成功添加。
此时可以直接输入 mytest 就可以成功运行程序。
当然也可以将可执行程序拷贝到系统的默认路径下。相当于 Linux 下软件的安装。
💖 USER 环境变量
用户登陆的时候,系统中有 USER 环境变量可以确定当前用户是谁。
当访问某些文件时,环境变量中记录了当前使用 Linux 的用户是谁,再和文件的拥有者和所属组对比,然后再确定是否有权限读写执行。
💖 查看系统的环境变量
可以使用 env 查看系统的环境变量。env 命令可以查看当前 Linux 用户的环境变量。
🙊 环境变量获取 🙊
💖 命令行第三个参数
系统启动的时候已经存在大量的环境变量,可以通过程序获取这些环境变量。main 函数可以传环境变量表。
char* envp[ ] 是一个指针数组,数组里存放的是一个个 char* 类型的指针。指针只是一个地址,地址其实也是数据,而保存这个数据的变量就叫做指针变量。
凡是具有指向能力的数据都可以称为指针,而指针变量是一个变量,需要在内存中开辟四个字节的空间,具有数据存储和被修改的能力。
char envp[ ]* 里面保存若干个可以保存地址数据的元素。这些 char* 类型的指针元素可以指向某一个字符,还可以指向一个 char* 类型字符串的起始地址。
第一个无效内容必须以 NULL 结尾。
如下面一段代码:
#include <stdio.h>
#include <unistd.h>
int main(int argc[],char* argv[], char* envp[])
{
int i = 0;
for (i = 0; envp[i]; i++)
{
printf("envp[%d]->%s\n",i,envp[i]);
}
}
for 循环中 envp[ i ] 不用判断,因为 envp[ i ] 是在提取特定下标的内容,如果是有效内容指向的是地址,当其指向地址为 NULL (0) 时,就退出循环。
我们可以通过 main 函数的第三个参数提取环境变量表, 即 envp [ ] 是一张传递给当前进程的环境变量表。指针数组中元素指向的是环境变量字符串。
💖 通过第三方变量 environ 获取
如果 main 函数一个参数没有写,如何获取运行进程的环境变量?
还可以使用全局变量 environ 获取。
environ 是 char** 类型,因为 main 函数的参数 char* envp[ ] 代表的是指针数组的地址,也可以是指针数组首元素的地址,而指针数组存的是 char* 类型的指针,指针的地址是一个二级指针 char**,所以本质上 char** 和 char* envp[ ] 是同一个地址。看下面一段代码:
#include <stdio.h>
#include <unistd.h>
int main()
{
int i = 0;
extern char** environ;
//environ[i] == *(environ + i)
for (i = 0; environ[i]; i++)
{
printf("environ[%d]->%s\n",i,environ[i]);
}
}
运行结果如下:
我们发现也可以使用 environ 来获得所有的环境变量。所以程序内部就可以获得环境变量。
💖 通过函数获取环境变量
输入如下代码获取 USER 环境变量内容:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
char* user = getenv("USER");
if(user == NULL)
{
perror("getnv");
}
else
{
printf("USER:%s\n",user);
}
return 0;
}
代码运行结果如下:
写一个只可以自己执行而其他人不允许执行的程序:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define NAME "potato"
int main()
{
char* own = getenv("USER");
if(strcmp(own,NAME) == 0)
{
printf("这个程序已经执行了。。。\n");
}
else
{
printf("当前用户 %s 是一个非法用户,无法执行\n",own);
}
return 0;
}
当用户是自己的时候,程序可以执行
当用户是 root 的时候,程序不可以执行
所以程序中可以存在系统提供的环境变量,可以结合环境变量写出与系统相关的代码,这是环境变量的基本应用。
🙊 什么是环境变量 🙊
💖 特点
1、环境变量就是内存级的一张表,用户在登陆系统的时候,给特定用户形成属于自己的环境变量表。
2、环境变量中的每一个变量都有自己的用途,有的是进行路径查找的,有的是进行身份认证的,有的是进行动静态库查找的,有的是为了确认当前路径的等等,每个环境变量都有自己的应用场景。
3、环境变量每一个元素都是 KV(variable = value)结构的。
💖 环境变量数据来源
那么环境变量对应的数据是从哪里来的呢?
是从系统相关配置文件中读取来的。
首先 cd ~ 回到家目录,然后执行 ls -al 命令,可以看到 .bash_profile 和 .bashrc 两个配置文件。
这两个配置文件本质就是 shell 脚本。.bash_profile 会调用 .bashrc,.bashrc 又会调用系统内部 /ect 文件夹下的 bashrc。
当用户登陆的时候,登陆程序会自动执行当前用户下的配置文件,环境变量 PATH 也会被加载。
而 etc 目录下的 bashrc 是全局环境变量。
里面是一些脚本,可以先不用了解。
当在命令行输入命令时,都是由 bash 执行,bash 除了能执行命令,也支持命令行式的自定义变量,如在命令行中写 mycal=100,就相当于在 bash 内部定义了一个 myval 和 100。
当一个用户登陆成功时,操作系统会创建一个提供命令行解释功能的 shell,而 shell 是一个可以读取命令和命令行的进程,比如我们输入 myval=100,shell 就会读取到这个字符串,然后 shell 内部会维护刚刚提到的 char* envp[ ] 数组,shell 可以从数组中找一个未使用的元素指向字符串,而环境变量是一张表,通过这种方式就可以将变量添加到环境变量表中。
如果不是环境变量,就申请一块空间将字符串存放到 shell 内。
shell 会执行命令,所有命令都是 shell 的子进程,当 shell 执行命令时,父进程 shell 会将表中的环境变量交给子进程。
下面我们通过一个示例验证下:
在命令行中输入 export hello=‘youcanseeme’ 导入一个环境变量,使用 env 指令就可以看到新增的 hello 环境变量。
导入环境变量实际上是将其设置到 shell 进程内部,当 shell 解释这个命令时,将 hello=youcanseeme 当作字符串并将其放入到环境变量表中,而 shell 启动时是从系统的配置文件中读取到环境变量表并初始化。所以 env 查看的环境变量和 export 寻找的环境变量都是 shell 中的环境变量。
如何证明环境变量表被子进程拿到呢?
看如下代码:
int main()
{
printf("myenv:%s\n",getenv("hello"));
return 0;
}
当前代码编译运行,就变成了 bash 的子进程,因为我们刚刚导入了一个 hello 的环境变量,如果父进程会将环境变量表交给子进程,此时子进程会获得 shell 中的 hello 环境变量,编译运行结果如下:
由以上结果得知,环境变量可以被所有相关的子进程继承,也就是说环境变量具有全局属性。如果自己在命令行导入了一个环境变量,子进程也可以使用这个环境变量。
在命令行中输入 hello1=123456,输入 echo $hello1,结果如下:
再修改 test.c 内容如下:
int main()
{
printf("myenv:%s\n",getenv("hello1"));
return 0;
}
编译运行结果如下:
发现如果不加 export 并不能被子进程继承。因为加 export 就是将变量变成环境变量,可以被子进程继承。
不加 export 创建的变量是 shell 的本地变量,只在 shell 内部有效,不可以被子进程继承。如果想让子进程继承,就需要输入 export hello1,此时再运行 mytest 就可以正常打印。
export 就是将 bash 的本地变量添加到环境变量表中。
环境变量存在于系统的配置文件中,当启动 bash 后,bash 会读取配置文件将环境变量加载到 bash 内部,后面输入命令(启动 bash 子进程)会将 bash 的环境变量加载到子进程。
🙊 命令行参数 🙊
💖 命令行参数说明
char* argv[ ] 是一张表,包含指向字符串的指针,argc 代表 argc 数组中元素的个数。看如下代码:
int main(int argc, char* argv[])
{
for(int i = 0; i < argc; ++i)
{
printf("argv[%d]->%s\n",i,argv[i]);
}
return 0;
}
运行结果如下:
在命令行中以空格作为分隔符输入几个字符串 ./mytest、a、b、c。第一个字符子串是可执行程序,后面的三个字符串成为参数选项,而 argv 是一个表,表中的指针元素会依次指向字符子串。
bash 拿到字符串制作了这个表供子进程使用。
💖 命令行参数作用
写一个需要传递一个参数的代码:
void Usage(const char* name)
{
printf("\nUsage:%s -[a|b|c]\n",name);
exit(0);
}
int main(char argc, char* argv[])
{
if(argc != 2)
Usage(argv[0]);
if(strcmp(argv[1],"-a") == 0)
printf("打印当前目录下的文件名\n");
else if(strcmp(argv[1],"-c") == 0)
printf("打印当前目录下文件的详细信息\n");
else if(strcmp(argv[1],"-c") == 0)
printf("打印当前目录下的文件名(包含隐藏文件)\n");
else
printf("其他功能,待开发\n");
return 0;
运行结果如下:
由此可以看出命令有自己的选项,这些选项以字符串的形式通过命令行参数传递给程序,对应程序内部对选项做判断,这样就可以让相同的程序提供不同的选项表现出不同的现象或执行结果。
🙊 进程的优先级 🙊
💖 基本概念
1、cpu 资源分配的先后顺序,就是指进程的优先权(priority)。
2、优先权高的进程有优先执行权利。配置进程优先权对多任务环境的 linux 很有用,可以改善系统性能。
3、还可以把进程运行到指定的 CPU 上,这样一来,把不重要的进程安排到某个 CPU,可以大大改善系统整体性能。
💖 优先级和权限
权限代表能或不能的问题,优先级是能的基础上谁先谁后的问题。因为 cpu 资源有限,而进程很多,由少量资源应对多量进程,就存在竞争分清谁先谁后。
可以使用 ps -l 命令查看当前系统中的进程优先级,这个优先级存在于进程的 task_struct ( pcb ) 中,其中 PRI 代表当前进程的优先级,NI 值代表当前进程的优先级的修正数据。UID 是当前用户的身份标识。
💖 PRI 和 NI
1、PRI 也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被 CPU 执行的先后顺序,此值越小进程的优先级别越高
2、NI 就是 nice 值,其表示进程可被执行的优先级的修正数值
3、PRI 值越小越快被执行,那么加入 nice 值后,将会使得 PRI 变为:PRI ( new ) = PRI ( old ) + nice 这样,当 nice 值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
4、调整进程优先级,在 Linux 下,就是调整进程 nice 值
5、nice 其取值范围是 -20 至 19,一共 40 个级别
写出如下代码:
int main(int argc, char* argv[])
{
while(1)
{
printf(".");
fflush(stdout);
sleep(1);
}
}
将程序运行,并用 ps -al 命令查看进程。
输入 top 命令 再输入 r ,输入进程 PID 设置对应进程的优先级。
输入 -10 进行调整并退出,再次输入 ps -al 命令查看发现优先级已经调整完毕。
💖 其他概念
1、竞争性: 系统进程数目众多,而 CPU 资源只有少量,甚至 1 个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
2、独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
3、并行: 多个进程在多个 CPU 下分别,同时进行运行,这称之为并行
4、并发: 多个进程在一个 CPU 下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发