--------------------------------------------------------------------------------------------------------------------------------
每日鸡汤:Never frown, even when you are sad, because you never know who is falling in love with your smile
--------------------------------------------------------------------------------------------------------------------------------
前言
前面我们已经学习了进程的各种性质:
- 竞争性:系统进程数目众多,而CPU资源只有少量甚至一个,所以进程之间具有竞争属性的,为了高效地完成任务,更合理竞争相关资源,便具有了优先级
- 独立性:多程序运行需要独享各种资源,多进程运行期间互补干扰(多个程序,一个程序关闭,不影响另一个程序功能)
- 并行:多个进程在多个CPU下分别,同时进行运行,这称为并行
- 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间内,让多个进程都需要得以推进。
一:并发的理解
多个进程在一个CPU上采用进程切换的方式运行,所以当一个进程被调度到CPU上运行,若在一个时间片内运行完毕,则将该进程释放销毁,若在一个时间片时间内仍然没有运行完毕,则会采用进程切换的方式(根据优先级将其放入到等待队列相应的优先级链表当中),即排队。这就是基于进程切换的时间片轮转的调度算法。
排队(优先级):一个进程从CPU上剥离下来的,该进程放到等待队列。这就是要维护两个队列的原因。
1.1:时间片
时间片(Timeslice)是操作系统中用于实现多任务处理的一种机制。在分时系统中,CPU的时间被分割成许多小的片段,称为时间片。每个进程或线程会被分配一个时间片,在这段时间内它可以独占使用CPU资源。当这个时间片用完后,操作系统会暂停当前进程,并将控制权交给下一个等待执行的进程,这样就实现了多个进程看似同时运行的效果。
时间片轮转调度算法
时间片轮转调度算法(Round Robin Scheduling)是一种常见的基于时间片的调度策略。在这种算法下,每个进程都会获得一个固定长度的时间片来执行。如果在这个时间片结束时进程还没有完成其工作,那么它就会被中断,放入队列的末尾,等待下一次被调度。这种方式可以确保所有进程都能得到公平的CPU时间。
时间片的长度对系统的性能有直接影响。如果时间片太短,会导致频繁的上下文切换,增加开销;如果时间片太长,则可能导致响应性变差,因为其他进程需要等待更长时间才能得到CPU时间。因此,选择合适的时间片长度对于优化系统性能至关重要。
1.2:上下文进程切换
1.2.1:寄存器
在谈上下文切换时要先说一下有关寄存器的理解。
因为CPU有许多寄存器,那么就有两个问题:
- 为什么函数返回值,会被外部拿到呢?
- 系统如何得知我们的进程当前执行到哪行代码了?
问题一:为什么函数返回值,会被外部拿到呢?
看代码汇编:
return a; //汇编语言:mov eax 10
即: 返回值是通过CPU内寄存器拿到的。
问题二:系统如何得知我们的进程当前执行到哪行代码了?
CPU内有一个计数寄存器PC。
程序计数器PC[eip](CPU内部寄存器):记录当前程序正在执行指令的下一行指令的地址。
因为PC寄存器保存当前程序正在执行指令的下一行指令的地址。即通过PC寄存器可以随时知道当前程序运行的位置。这样系统就能够得知我们当前进程执行代码的位置。
CPU内部有着一整套寄存器:
通用寄存器:eax ebx ecx edx ...
栈帧结构寄存器:ebp esp eip ...
状态寄存器:status ...
........
1.2.2:上下文切换
那既然有那么多的寄存器,那么寄存器在计算机当中扮演什么样的角色呢?
=> 提高效率,进程的高频数据放入寄存器中。
即,CPU内的寄存器里面保存的是进程相关的数据,来对数据进行访问和修改
故:CPU寄存器里面保存的是进程的临时数据——也就是进程的上下文数据
引用一个小故事加深理解:
进入大学,军队机构从大学内选拔兵士,你非常幸运的被选拔上了。选拔上之后就要去军队机构报道进入军队。但是进入军队之前还要去学校的院长办公室告诉校长保存我的学籍。当兵之后要回到学校继续学习。首先就要先到校长办公室来开启你的学籍情况(避免已经上了一年多了,但是你的学籍还被封密保存的情况)。
同理进程:
进程 ——> 在CPU运行一个时间片完毕后进程没有执行 完——> 将进程当前位置的上下文数据[寄存器]保存好 ——> 当再次运行该进程的时候,会根据上下文数据[寄存器]找到上次没运行到的位置接着运行。
所以上下文切换:
- 进程在从CPU离开的时候,要将自己的上下文数据保存好,甚至带走。【保存的目的,未来都是为了恢复】。
- 进程在被切换的时候,要有两个阶段——1.保存上下文;2.恢复上下文)一般是指在操作系统中用来指定操作系统运行环境的一些参数.
- 第二次运行该进程时,会接着第一次运行该进程的所保存的最后的位置。
而在寄存器上含有关于寄存器的属性变量。
寄存器在CPU上,而在PCB内部的属性中的某一个“变量”属用来描述寄存器的各种属性的结构体。
二:认识几个环境变量
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
2.1:PATH
为什么写完一个可执行程序后将其运行要./mycmd,而mycmd则是错误的。
发现问题:并没有找到mycmd的命令。
通过 which 指令找搜索路径:
可发现which pwd,能找到pwd指令路径/usr/bin/pwd。
但是which mycmd,该命令没有在(/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/alin/.local/bin:/home/alin/bin)内部中。那么这一串路径到底是什么呢?
看PATH环境变量?
一模一样!!!
这就引出了PATH环境变量的概念了。
PATH:Linux系统的指令默认搜索路径,即输入一串命令,首先要在PATH内部的一大串路径中的各个子路径去寻找(子路径与子路径之间用:分割)
echo $PATH 查看PATH内部的路径情况
那么怎样才能直接输入 mycmd,运行当前可执行程序呢。这就需要对PATH环境变量做修改(两种方法)。
- PATH=/Home/alin/test 覆盖PATH内部的路径
- PATH=$PATH:/home/alin/test 给PAHT增加一个默认路径路径
对于直接覆盖PATH内部的路径,是不建议的。覆盖完之后,会导致很多指令都不可用了。虽然退出xshell后重新登录会恢复,但还是很冒失麻烦。
给PAHT增加一个默认路径路径(mycmd):
增加了之后,直接输入 mycmd就会执行该可执行文件。
2.2:HOME
环境变量HOME:查看当前用户家目录
- echo $HOME root用户输出 /root
- echo $HOME 普通用户输出 /home/普通用户的名字
cd $HOME 进入家目录
2.3:SHELL
SHELL是一个环境变量,通常用于指定用户默认使用的 shell 程序。在 Unix 和类 Unix 系统(如 Linux 和 macOS)中,常见的 shell 包括
bash
、zsh
、sh
、ksh
等。
我们是如何知道当前使用的是哪一个shell呢?
那么,SHELL环境变量保存的是shell当前版本的可执行程序
直接输入 echo $SHELL来查看当前用户的默认SHELL
2.4:HISTSIZE
HISTSIZE是一个环境变量,用于控制 shell 历史记录中存储的命令数量。这个变量通常在 bash和其他类似的 shell 中使用。
即,就是用来记录历史命令的。
输入:echo $HISTSIZE
2.5:env指令
查看所有的环境变量
输入 env 命令查看所有的环境变量
如何一个环境变量呢?getenv函数
查看getenv函数用法:man getenv 命令
用法:getenv("环境变量")
在mycmd.c文件:
查看运行结果:
所以:因为有了环境变量的存在,我们的系统就具备了认识当前用户是谁的能力,只要认识当前用户是谁,就可以和文件属性的拥有者、所属组做对比,继而判定你有没有读和写的能力。
三:环境变量
3.1:环境变量的理解
环境变量是系统提供的一组name=value形式的变量,不同的环境变量有不同的用户,通常具有全局属性。
环境变量在系统启动的时候就已经有了。
那么全局属性到底是什么?
3.2:全局属性(命令行参数)
在理解全局属性之前要谈一个命令行参数。
平时在我们写的 main函数内部含有三个参数。agrc,argv,env。
int main(int argc, char* argv[], char* env[])
{// ...
}// argc:int类型
argv:指针数组
env:指针数组
char* argv[]:指针数组。内部有 argc 个元素。
并且发现是在Linux命令行上写的指令。
./mycmd -a -b -c <==> "./mycmd -a -b -c" <==> 以空格为分割[bash做的打散]:"./mycmd" "-a" "-b" "-c"
在argv向量表内部
- argv[0] = "./mycmd"
- argv[1] = "-a"
- argv[2] = "-b"
- argv[3] = "-c"
那么Linux系统内bash为什么要这样干(打散)? => 为指令工具、软件等提供命令行选项的支持!
比如以 ls 指令为例:
- ls -a
- ls -l
- ls -a -l -d
即,通过不同的选项实现不同的功能。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[], char* env[])
{if(argc!=2){printf("Usage:%s -[a/b/c/d]\n",argv[0]);return 0;}if(strcmp(argv[1],"-a")==0){printf("功能一[-a]\n");}else if(strcmp(argv[1],"-b")==0){printf("功能二[-b]\n");}else if(strcmp(argv[1],"-c")==0){printf("功能三[-c]\n");}else if(strcmp(argv[1],"-d")==0){printf("功能四[-d]\n");}else{printf("default功能\n");}return 0;
}
第三个参数 char* env[]【环境变量列表】
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main(int argc, char* argv[], char* env[])
{int i = 0; for(; env[i]; i++) { printf("env[%d] => %s\n",i, env[i]); } return 0; }
运行mycmd程序
发现env指针数组内存储的是全部的环境变量。
当main函数无参的时候还有另一种方式查看环境变量(environ):
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>int main()
{//无参调用环境变量extern char** environ;int i=0;for(; environ[i]; i++){printf("environ[%d] = %s\n",i,environ[i]);}return 0;
}
运行结果(正常显示出环境变量):
总结:
C/C++一共有两张核心向量表
- 第一张表:命令行参数表【命令行选项】
- 第二张表:环境变量表【环境变量】
运行该程序时,一定会先将这两张表传过去。
即:我们所运行的进程都是子进程,bash本身在启动的时候会从操作系统的配置文件中读取环境变量信息。子进程会继承父进程交给我的所有环境变量。
所以,环境变量会从所有子进程中一直继承下去。这就是全局属性!!!
验证?
创建一个子进程:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main(int argc, char* argv[], char* env[])
{pid_t id = fork();if(id==0) //在子进程内{int i = 0;for(; env[i]; i++){printf("env[%d] => %s\n",i, env[i]);}}return 0;
}
四:本地变量&内建命令
4.1:本地变量
在命令行中直接定义
本地变量:不会被子进程继承,只会在本地bash内部有效。
直接在命令行中输入:
- a=1
- b=2
- ......
查看a变量和b变量:
- echo $a
- echo $b
查看系统中所有的变量【本地变量 + 环境变量】
set 指令:
这样即可查看系统内所有的变量,包括环境变量和本地变量。
将本地变量转换成为环境变量(export)
将本地变量a转换成为环境变量。
4.2:内建命令
Linux系统中有两批命令:
- 常规命令——通过创建子进程完成的
- 内建命令——bash不创建子进程,而是由自己亲自执行,类似于bash调用自己写的或有系统提供的函数
看一个例子:
- my_data=666
- echo $my_data //bash的子进程
echo也是一条指令,它要不要创建子进程?
=> 答:不需要,它类似于一个函数,类似于是bash内部的一个函数
即,echo是一个内建命令
所以,echo指令是由bash亲自执行的。不是创建的子进程完成的。所以有能查看本地变量的功能.
相应的我们学的cd变量也是内建命令,【即可以在程序内部,修改路径】
如何验证cd内建命令呢? 使用 chdir 函数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[], char* env[])
{sleep(30); //留下时间操作printf("change begin:\n");if(argc==2){chdir(argv[1]); //更改成这个路径}printf("change end\n");sleep(30);return 0;
}
修改默认当前路径为根目录/