主页:醋溜马桶圈-CSDN博客
专栏:Linux_醋溜马桶圈的博客-CSDN博客
gitee:mnxcc (mnxcc) - Gitee.com
目录
1.环境变量
1.1 基本概念
1.2 常见环境变量
1.3 查看环境变量方法
1.4 和环境变量相关的命令
1.5 环境变量的组织方式
1.6 通过代码如何获取环境变量
1.6.1 命令行第三个参数
1.6.2 通过第三方变量environ获取
1.7 通过系统调用获取或设置环境变量
1.8 环境变量通常是具有全局属性的
2.程序地址空间
2.1 研究背景
2.2 程序地址空间回顾
2.3 进程地址空间
3.Linux2.6内核进程调度队列
3.1 一个CPU拥有一个runqueue
3.2 优先级
3.3 活动队列
3.4 过期队列
3.5 active指针和expired指针
3.6 总结
1.环境变量
1.1 基本概念
- 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
- 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
- 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
1.2 常见环境变量
- PATH : 指定命令的搜索路径
- HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
- SHELL : 当前Shell,它的值通常是/bin/bash
1.3 查看环境变量方法
echo $NAME //NAME:你的环境变量名称
1.4 和环境变量相关的命令
- echo: 显示某个环境变量值
- export: 设置一个新的环境变量
- env: 显示所有环境变量
- unset: 清除环境变量
- set: 显示本地定义的shell变量和环境变量
1.5 环境变量的组织方式
每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串
1.6 通过代码如何获取环境变量
1.6.1 命令行第三个参数
#include <stdio.h>
int main(int argc, char* argv[], char* env[])
{
int i = 0;
for (; env[i]; i++) {
printf("%s\n", env[i]);
}
return 0;
}
1.6.2 通过第三方变量environ获取
#include <stdio.h>
int main(int argc, char* argv[])
{
extern char** environ;
int i = 0;
for (; environ[i]; i++) {
printf("%s\n", environ[i]);
}
return 0;
}
libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明
1.7 通过系统调用获取或设置环境变量
- getnev
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}
- 常用getenv和putenv函数来访问特定的环境变量
1.8 环境变量通常是具有全局属性的
- 环境变量通常具有全局属性,可以被子进程继承下去
#include <stdio.h>
#include <stdlib.h>
int main()
{
char* env = getenv("MYENV");
if (env) {
printf("%s\n", env);
}
return 0;
}
直接查看,发现没有结果,说明该环境变量根本不存在
- 导出环境变量
export MYENV="hello world"- 再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!
2.程序地址空间
2.1 研究背景
- kernel 2.6.32
- 32位平台
2.2 程序地址空间回顾
在C语言的时候,有这样的空间布局图
可是我们对他并不理解
来段代码感受一下
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if (id < 0) {
perror("fork");
return 0;
}
else if (id == 0) { //child
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
else { //parent
printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}
输出结果:
//与环境相关,观察现象即可
parent[2995]: 0 : 0x80497d8
child[2996] : 0 : 0x80497d8
我们发现,输出出来的变量值和地址是一模一样的,很好理解呀,因为子进程按照父进程为模版,父子并没有对变量进行进行任何修改。可是将代码稍加改动:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if (id < 0) {
perror("fork");
return 0;
}
else if (id == 0) { //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取
g_val = 100;
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
else { //parent
sleep(3);
printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}
输出结果:
//与环境相关,观察现象即可
child[3046]: 100 : 0x80497e8
parent[3045] : 0 : 0x80497e8比
我们发现,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论:
- 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
- 但地址值是一样的,说明,该地址绝对不是物理地址!
- 在Linux地址下,这种地址叫做 虚拟地址
- 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理
OS必须负责将 虚拟地址 转化成 物理地址
2.3 进程地址空间
所以之前说‘程序的地址空间’是不准确的,准确的应该说成 进程地址空间
说明:
上面的图就足矣说名问题,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!
3.Linux2.6内核进程调度队列
上图是Linux2.6内核中进程队列的数据结构
3.1 一个CPU拥有一个runqueue
- 如果有多个CPU就要考虑进程个数的负载均衡问题
3.2 优先级
- 普通优先级:100~139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!)
- 实时优先级:0~99(不关心)
3.3 活动队列
- 时间片还没有结束的所有进程都按照优先级放在该队列
- nr_active: 总共有多少个运行状态的进程
- queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!
从该结构中,选择一个最合适的进程,过程是怎么的呢?
- 从0下表开始遍历queue[140]
- 找到第一个非空队列,该队列必定为优先级最高的队列
- 拿到选中队列的第一个进程,开始运行,调度完成!
- 遍历queue[140]时间复杂度是常数!但还是太低效了!
- bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率
3.4 过期队列
- 过期队列和活动队列结构一模一样
- 过期队列上放置的进程,都是时间片耗尽的进程
- 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算
3.5 active指针和expired指针
- active指针永远指向活动队列
- expired指针永远指向过期队列
- 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的
- 没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程!
3.6 总结
- 在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成本增加,我们称之为进程调度O(1)算法