一、 进程优先级
0x01 什么叫进程优先级
CPU资源分配的先后顺序
0x02 为什么要有进程优先级
因为资源不足,是分配资源的一种方式,优先权高的进程有优先执行权利
0x03 查看更加详细的进程信息
①运行代码
#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{
while(1)
{
cout << "PID:" << getpid() << " " << "PPID:" << getppid() << endl;
sleep(1);
}
return 0;
}
②ps -al 查看更加详细的进程信息
③几个重要信息
UID : 代表执行者的身份PRI :代表这个进程可被执行的优先级,其值越小越早被执行NI :代表这个进程的 nice值,是优先级的一个修正数据,取值范围是 -20 至 19 ,一共 40 个级别
0x04 调整优先级
①运行程序
②输入top命令,输入r ,输入进程号
③输入nice值
如果我们再进行一次调整会是什么样的呢?
我们知道PRI(new)=PRI(old)+nice,但是为什么不是在90上再加5呢?
因为旧的优先级默认为80
0x05 nice值为何要是一个相对较小的范围呢?
因为优先级再怎么设置,也只能是一种相对的优先级,不能出现绝对的优先级,否则会出现很严重的饥饿问题,即某一个进程长时间得不到调度。而调度器的作用就是:较为均衡的让每个进程享受到CPU资源
0x06 小结
①系统进程数目众多,具有竞争性,而CPU资源只有少量,甚至只有1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理金正相关资源,便具有了优先级
②独立性: 多进程运行期间,互不干扰
③并行: 多个进程在多个CPU下同时运行
④并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进
二、环境变量
0x01 引入
./test,为什么用"./ " ?
因为要帮系统确认对应的程序在哪里
那为何系统的命令不用路径呢?
因为环境变量:指在操作系统中用来指定操作系统运行环境的一些参数
PATH : 指定命令的搜索路径
查看环境变量:
0x02 那么我们可以不用./,就能将程序运行起来吗?
当然可以,我们可以将路径导入到环境变量中去(只要没有改配置文件,那么重启时就会恢复默认).
三种方法:
①可以将程序test放入/usr/bin目录中
②如果想让导入的环境变量长期有效,可以在vim ~/.bash_profile中去修改
③export PATH=$PATH: 路径,将路径导入环境变量(建议)
0x03 HOME(常用环境变量)
HOME : 指定用户的主工作目录 ( 即用户登陆到 Linux 系统中时 , 默认的目录 )
0x04 SHELL (常用环境变量)
SHELL : 当前 Shell版本或者Shell命令,它的值通常是/bin/bash
0x05 与环境变量相关的命令
1. echo: 显示某个环境变量值2. export: 设置一个新的环境变量3. env: 显示所有环境变量4. unset: 清除环境变量5. set: 显示本地定义的shell变量和环境变量
0x06 小结
①环境变量的本质是操作系统在内存或者磁盘文件中开辟的空间,用来保存系统相关的数据
②环境变量 = 变量名 + 变量内容
③系统上还存在一种变量,是与本次登录有关的变量,只在本次登录有效(本地变量)
④可以将本地变量导入环境变量(重启也会释放掉这个变量)
三、main函数参数
0x01 argc&argv
#include<stdio.h>
int main(int argc,char* argv[])
{
for(int i = 0;i < argc;i++)
{
printf("argv[%d] -> %s\n",i,argv[i]);
}
return 0;
}
[wh@bogon lesson10]$ ./test -a -b -c -d
argv[0] -> ./test
argv[1] -> -a
argv[2] -> -b
argv[3] -> -c
argv[4] -> -d
0x02 命令行参数的作用
同一个程序我们可以给他带入不同参数的方式来让他呈现出不同的表现形式 ,即输入不同的选项呈现出不同的结果
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main(int argc,char* argv[])
{
if(argc != 2)
{
printf("Usage: %s -[a|h]\n",argv[0]);
return 1;
}
if(strcmp(argv[1],"-h") == 0 )
{
printf("hello -h\n");
}
else if(strcmp(argv[1],"-a") == 0)
{
printf("hello all\n");
}
else
{
printf("hello world\n");
}
}
指令有很多选项,用来完成同一个命令的不同子功能,选项底层使用的就是我们的命令行参数
0x03 环境变量的组织方式
当然main函数的参数也可以是环境变量env
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main(int argc,char* argv[],char* env[])
{
for(int i = 0;env[i]; i++)
{
printf("%d -> %s\n",i,env[i]);
}
return 0;
}
当然也可以不用main函数的第三个参数的方式,查看环境变量,也可以通过第三方变量environ来获取
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main(int argc,char* argv[])
{
extern char** environ;
for(int i = 0;environ[i]; i++)
{
printf("%d -> %s\n",i,environ[i]);
}
return 0;
}
0x04 getenv()
用来获取环境变量
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char* argv[])
{
printf("PATH: %s\n",getenv("PATH"));
return 0;
}
四、环境变量通常具有全局属性
0x01 启动进程的父进程是什么呢?
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
int main()
{
while(1)
{
printf("PID:%d,PPID:%d\n",getpid(),getppid());
sleep(2);
}
return 0;
}
命令行上启动进程,父进程都是bash
0x02 环境变量是可以被继承的
①开始设置的my_env是本地变量,所以使用子进程./test找不到
②之后将其导入环境变量,既可以进行env查询,也可以通过./test查询
③可以说明,将my_env导成环境变量,其实是导给了bash中的列表
④而我们的子进程test就是继承父进程的环境变量,所以./test可以查询到此环境变量
⑤从而说明环境变量是可以被子进程继承的
小结:
环境变量具有全局属性,可以被子进程继承下去,影响了整个用户系统
五、程序地址空间&进程地址空间
0x01 程序地址空间布局
#include<stdio.h>
#include<stdlib.h>
int g_unval;
int g_val = 100;
int main()
{
const char* s = "hello world";
printf("code addr: %p\n",main);
printf("Character constant addr: %p\n",s);
printf("uninit addr: %p\n",&g_unval);
printf("init addr: %p\n",&g_val);
char* heap = (char*)malloc(10);
printf("heap addr: %p\n",heap);
printf("stack addr: %p\n",&s);
printf("stack addr: %p\n",&heap);
int a = 10;
int b = 30;
printf("stack addr: %p\n",&a);
printf("stack addr: %p\n,%b");
return 0;
}
0x02 虚拟地址引入
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int g_val = 100;
int main()
{
if(fork() == 0)
{
//child
int cnt = 5;
while(cnt)
{
printf("I am child,times: %d,g_val = %d,&g_val = %p\n",cnt,g_val,&g_val);
cnt--;
sleep(1);
if(cnt == 3)
{
printf("############child change before!##############\n");
g_val = 200;
printf("############child change after!##############\n");
}
}
}
else
{
//parent
while(1)
{
printf("I am parent,g_val = %d,&g_val = %p\n",g_val,&g_val);
sleep(1);
}
}
return 0;
}
从上述结果来看,因为写时拷贝,俩个进程互不干扰,所以当子进程修改值时,父进程并没有影响,但是我们也可以发现他们的地址是完全一样的。
但是地址怎么会没有变化呢?
如果C/C++打印出来的地址是物理内存的地址,这种现象可能存在吗?
当然不可能,所以我们使用的地址,绝对不是物理地址,即不是内存地址空间,而是进程虚拟地址空间
所以说,我们之前说的程序地址空间是有些不准确的,准确的应该说是进程地址空间
我们用c/c++语言所看到的的地址,全部都是虚拟地址,物理地址用户是看不到的,有操作系统统一管理,将虚拟地址转化成物理地址
0x03 进程虚拟地址空间
①进程&操作系统
操作系统相当于大富翁
物理内存相当于大富翁的所有钱,可以看作大富翁的银行
进程地址空间相当于大富翁画的大饼
进程相当于大富翁的私生子
接下来,我们就可以将其串联起来:
假如大富翁有100亿,都存在银行中,大富翁给三个私生子都画了个大饼,因为私生子之间互不认识,所以大富翁说,你们每个人都可以使用100亿,所以私生子们就都在规划这100亿该怎么使用,而三人之间又互不影响,都认为自己独占100亿
这将其换成进程与操作系统就更加贴切了:
操作系统掌控着所有物理内存空间,给每一个进程都画了一个进程地址空间(独占物理内存)这样的大饼,而每个进程之间又互不影响,都认为自己独占物理内存
此时又出现了一个问题:
② 每个进程都有一个地址空间,而系统中又存在大量的地址空间,那么操作系统应该怎样去管理这些地址空间呢?
先描述,再组织,而先描述其实用的是结构体mm_struct来描述地址空间
每个进程都认为mm_struct代表整个内存,都认为进程地址空间是按照4GB划分的
③那么我们该如何划分区域呢?
struct mm_struct
{
unsigned int code_start;
unsigned int code_start;
.....
unsigned int stack_start;
unsigned int stack_end;
}
地址空间上进行区域划分时,对应的线性位置虚拟地址
④为什么要有地址空间?
①通过添加一层软件层,完成有效的对进程操作内存进行风险管理(权限管理),本质目的是为了,保护物理内存以及各个进程的数据安全(如果进程直接和物理进程进行交互,那么怎么能保证这个进程不去访问或修改物理内存中不属于他的部分,所以增加地址空间可以对进程操作内存进行风险管理)
②将内存申请和内存使用的概念在时间上划分清除,通过虚拟地址空间,来屏蔽底层申请内存的过程,达到进程读写内存和操作系统进行内存管理操作,进行软件上面的分离(如果申请1000字节,进程其实是在地址空间上进行开辟的,但是当进程要使用这1000字节的空间,那么操作系统会让进程等一等,通过映射,在物理内存上申请好这1000字节的空间,而这样的申请也是基于缺页中断的物理内存的申请,然后再供进程进行使用)
③站在CPU和应用层的角度,进程可以看作统一使用4GB空间,而且每个空间区域的相对位置是比较确定的(程序的代码和数据可以被加载到物理内存的任一位置,因为页表可以通过映射,将地址空间上连续的地址,映射到物理内存中随意的位置,可以减少内存管理的负担)
操作系统这样设计的目的:每个进程都认为自己是独占系统资源的
⑤此时我们就可以解释一下为什么父子进程中值不同,但是地址相同的原因:
①因为子进程是以父进程为模板创建的,所以开始子进程物理地址也是指向父进程g_val位置,所以这也是前3秒,父进程和子进程打印的值相同的原因。
②之后子进程的值改成了200,又因为进程是具有独立性的,父进程与子进程之间互不干扰,所以此时就发生了写时拷贝,在物理内存中重新开辟了一块空间,给子进程进行使用,然后再重新进行虚拟地址与物理地址之间的映射(写时拷贝的原因)
③所以父进程与子进程打印的地址是一样的,是虚拟地址没有改变
④父子进程一般代码是共享的,此时就可以知道,都是通过映射关系,指向了同一块物理空间中的代码,所以只读的数据,一般可以只有一份,因为这是操作系统维护成本最低的