贵州遵义企业公司网站建设,免费商标图案设计大全,北京海淀网站制作,怎么做网站推广多少钱一 冯诺依曼体系结构
在此之前#xff0c;我们先要理解我们计算机的冯诺依曼体系结构#xff0c;因为是进程的基础
我们所有的操作其实都是基于这样一个模型#xff0c;比如你在qq上#xff0c;和别人发送消息#xff0c;这个消息肯定是先通过输入设备进行输入#xf…
一 冯诺依曼体系结构
在此之前我们先要理解我们计算机的冯诺依曼体系结构因为是进程的基础
我们所有的操作其实都是基于这样一个模型比如你在qq上和别人发送消息这个消息肯定是先通过输入设备进行输入输入到存储器这里只是单指内存然后通过控制器和运算器的控制和计算把消息发送到输出设备这里可以是网卡和显示器 那我们这些操作肯定需要有人控制吧没有人控制单凭一个cpu能去接受这么多信息别完了cpu的运行速度是很快的如果这些事情都要他去处理那就没有那么高效了。 所有我们引出了操作系统这个概念 二 操作系统
概念 任何计算机系统都包含一个基本的程序集合称为操作系统(OS)。笼统的理解操作系统包括 内核进程管理内存管理文件管理驱动管理 其他程序例如函数库shell程序等等 设计OS的目的 与硬件交互管理所有的软硬件资源 为用户程序应用程序提供一个良好的执行环境 定位 在整个计算机软硬件架构中操作系统的定位是一款纯正的“搞管理”的软件 由于我们的文件和进程错综复杂都会占据内存同时也有谁先进行谁后进行的顺序所以这里我们需要一个管理者去管理好这些资源不然我们的电脑用几下就会导致死机所以我们开机第一个启动的软件就是操作系统这个系统软件 管理
那我们操作系统是用什么方式去管理这些资源的呢??
这张图表示了我们在进行操作的轮廓图操作系统在管理底层的硬件的时候并不能直接去访问而是应该经过驱动程序间接访问所以操作系统在管理硬件资源的时候只是拿到了硬件的数据并没有直接见到了底层的硬件既然我们拿到了数据我们就可以通过数据去判断这个硬件程序是否需要关闭当然这些数据太多了而且有些属于一个类型有些不是那既然这样我们就可以通过描述这个硬件的基本属性同时加上他的数据这样归纳起来就可以统一管理了但是这么多硬件怎么去找呢创建一个双链表一个红黑树把他们管理起来可行
所以经过以上步骤就形成了一个先描述再组织的操作
之前说了只能从上到下访问那我们电脑上的软件有各种功能这些功能直接或者间接的操作了我们操作系统那是不是这些软件可以肆意妄为并不是操作系统会防止用户去乱动操作系统里面的东西但是它又得让用户去访问于是就有了系统调用这个概念由于系统调用的存在使得我们可以在电脑上开发各种应用使得我们的电脑功能性更全这种选择性提供也是操作系统管理的一种手段 总结 计算机管理硬件先用struct结构体描述再用多种数据结构进行组织这里为什么是struct呢因为linux是用c语言写的。
系统调用和库函数概念 在开发角度操作系统对外会表现为一个整体但是会暴露自己的部分接口供上层开发使用这部分由操作系统提供的接口叫做系统调用。 系统调用在使用上功能比较基础对用户的要求相对也比较高所以有心的开发者可以对部分系统调用进行适度封装从而形成库有了库就很有利于更上层用户或者开发者进行二次开发。 三 进程
有了上面的认识我们就可以再来认识进程的概念一个进程也就是我们所运行的程序一个qq一个微信或者一个英雄联盟他们都属于进程
所以一句话概括就是进程就是用来吃系统资源的——担当分配系统资源CPU时间内存的实体。
进程也是受操作系统所管理的那操作系统怎么管理和上面一样——先描述再组织
描述进程
对于进程我们得先描述。进程信息被放在一个叫做进程控制块的数据结构中可以理解为进程属性的集合。 课本上称之为PCBprocess control blockLinux操作系统下的PCB是: task_struct
这个PCB里面就包含了进程的诸多信息比如优先级上下文数据指针之类的
在linux下的PCB——task_struct ✨标示符: 描述本进程的唯一标示符用来区别其他进程。 ✨状态: 任务状态退出代码退出信号等。 ✨优先级: 相对于其他进程的优先级。 ✨程序计数器: 程序中即将被执行的下一条指令的地址。 ✨内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针 ✨上下文数据: 进程执行时处理器的寄存器中的数据[休学例子要加图CPU寄存器]。 ✨IO状态信息: 包括显示的I/O请求,分配给进程的IO设备和被进程使用的文件列表。 ✨记账信息: 可能包括处理器时间总和使用的时钟数总和时间限制记账号等。 ✨其他信息 组织进程
所有运行在系统里的进程都以task_struct链表的形式存在内核里。 查看进程
查看一个进程查看的是进程的信息
我们可以通过 ls /proc这个指令查看所有进程的信息 如果我们现在运行一个程序 如果我们想看他的信息那么我们就使用一个看起来比较直观的命令(ps/top)因为/proc这个指令可能看起来并不直观因为/proc 目录下的文件和目录提供的是系统的底层信息它们的内容通常是以文本形式展示的但格式和结构可能相对复杂。
这里我们使用ps查看我们先运行这个程序 这里可能查看到三个进程但是只有第一个是我们要查的其他两个是因为我们输入的指令也是一个进程所以也把他们的进程信息输出了 这就是进程的pid也就是进程的标识符linux也是通过这个去区分进程的不同的
除了这种方法可以获取进程的pid我们还可以通用系统调用去查看 通过系统调用获取进程标示符
#include stdio.h
#include unistd.h
#includesys/types.h
int main()
{printf(pid:%d\n,getpid());return 0;
}通过这段代码就可以获取到该进程的pid 运行这段程序就可以获得该进程的pid但是我们注意到其实他还有一个表示符
这一段是他们的父进程的pid也就是ppid每个子进程都有一个父进程当然最大的父亲就是我们的bash
我们也可以通过系统调用把他的ppid也打印出来 #include stdio.h
#include unistd.h
#includesys/types.h
int main()
{printf(pid:%d ppid:%d\n,getpid(),getppid());return 0;
}
通过系统调用创建进程-fork初识
对于fork也是一个系统调用他的作用就是创建一个子进程
#include stdio.h
#include sys/types.h
#include unistd.h
int main()
{int ret fork();if(ret 0){perror(fork);return 1;}else if(ret 0){ //childprintf(I am child : %d!, ret: %d\n, getpid(), ret);}else{ //fatherprintf(I am father : %d!, ret: %d\n, getpid(), ret);}sleep(1);return 0;
}对于上面的代码来说因为fork创建子进程会有一个返回值如果创建成功对于子进程会返回0对于父进程会返回子进程的pid如果失败则是小于0的值
所以根据返回值的不同我们就可以去执行两块程序 fork之后的代码子进程和父进程是共享的
那现在可能会困惑为什么一个变量会有两个返回值就算是两个进程但是也就一个变量一个变量的地址是固定那么就不可能存在一个变量有两个值的情况
所以我们得先了解虚拟地址空间这个概念
文字说明我们创建进程的时候会有操作系统先给进程描述一个PCB在linux里也叫tast_struct
这里面包含了进程的信息既然是信息那么就会包括数据的存放地址所以这里这就有一个虚拟地址的指针指向这块内存这块内存就是我们之前认识到的什么栈和堆静态区数据段...这类的空间 这个空间并不是实际数据存放的空间它的出现只是为了方便管理形成统一性那我们怎么去找到物理内存呢也就是实际的地址这里采取的方式就算通过页表去映射把虚拟内存和物理内存的地址一 一 对应起来如果我们创建子进程操作系统会创建子进程的PCB,然后会把父进程的虚拟内存的表继承下来这样子进程和父进程虚拟内存相同但是有两份因为子进程和父进程的代码相同因为是继承下来的又因为进程之间是相互独立互不干扰的所以当子进程中的变量改变的时候会产生临时拷贝也就是对于发生改变的数据会在物理内存中产生一个新的位置然后再通过页表的映射改变对应的虚拟内存的映射这样就完成了两个变量有不同的值虽然他们的虚拟地址是相同的但是物理地址却是不同的
图片说明 这里的进程空间其实是内存的一种数据结构
注意这里不要理解为是一个存在的空间只是一种数据结构而已
进程地址空间就类似于一把尺子尺子的刻度由0x00000000到0xffffffff尺子按照刻度被划分为各个区域例如代码区、堆区、栈区等。而在结构体mm_struct当中便记录了各个边界刻度例如代码区的开始刻度与结束刻度如下图所示 进程状态 运行状态
进程 PCB 被调度到 CPU 运行队列中且已被分配 CPU 资源就叫做 ------ 运行状态
每个进程都有自己的PCB自己的数据构成当我们存在多个进程的时候我们就需要用特定的数据结构把他们组织起来比如链表平衡树之类的这样我们就可以很轻松的通过一个进程找到其他的进程
所以需要运行的进程会把他们的PCB放在CPU的运行队列中然后通过相应的数据结构组织起来为了更加形象下面给出一张图片说明 这张图就是用的双向链表把他们组织起来的可能还会用到其他的数据结构因为操作系统的组织比这个可复杂太多了
如果这个进程需要执行了就把代码和数据放进CPU中就行了
那如果这个进程运行了很久呢那不是一直等他完成
不是每一个进程都有一个叫做时间片的概念 其时间大概是在10 ms左右。所以并不是一个进程一直在执行而是这多个进程在一个时间段内所有代码都会被执行 —— 这就叫做【并发执行】 所以呢这就一定会存在大量的进程 被CPU放上去、拿下来的动作 —— 这就叫做【进程切换】
这个速度很快我们感受不到是很正常的
阻塞状态
阻塞 就是 进程 因等待某种条件就绪而导致的一种不推进状态比如等待 键盘输入。通俗的来说 阻塞 就是 进程卡住了原因就是缺少资源
注意这里要和上面运行状态的进程切换区分开上面的是在运行不缺条件这里是因为条件的缺失而去等待资源
那么进程需要什么资源呢
比如 磁盘、网卡、显卡 等各种外设假设你现在想在 steam 上下载游戏当你点击下载按钮后提示磁盘空间不足此时是无法运行 steam下载 这个进程的因为此 进程 需要等待足够大的 磁盘资源此时我们就称此 进程 为 阻塞 状态
和上面的注意关联起来 总结进程阻塞就是不被调度 此时 PCB(task_struct) 就会被设置为 阻塞状态并链入等待的资源提供的等待队列。没错这里的等待队列 类似于 CPU 运行队列 挂起状态
当 CPU 资源紧张时将 进程的数据和代码 交换至 磁盘 中挂起此时内存中只有 PCB 挂起 可以看作一种特殊的 -- 阻塞状态
可能你买苹果电脑的时候就会考虑内存够不够用的问题也许你从网上看了很多有说够用有说不够用所以这里很多人提到的词就swap这里的swap就是当我们的内存不够用的时候会把代码和数据放进磁盘里面等到需要用的时候再拿出来这个操作就算swap这个是有成本的所以多次的swap可能会导致电脑性能下降发热等等 当计算机资源比较吃紧时操作系统一定要确保自身不会因为资源的紧张而崩溃所以就会将一些等待资源阻塞的进程的代码和数据交换到磁盘的 swap分区 中这个过程称为唤出。当需要调度此进程时就会将磁盘的 swap分区 中保存的内容换回到内存中这个过程称为唤入。 注意交换的是进程的代码和数据不是PCB如果PCB被交换出内存了那操作系统如何管理呢
所以当某个进程的代码和数据不在内存中而被换出到磁盘上时该进程就为挂起状态。 linux系统下的7种进程状态
运行状态
我们先看一段代码 #include stdio.h#include unistd.hint main(void){while(1); {printf(Hello process, pid: %d\n,getpid());sleep(1);}return 0;}从图中可以看到状态是S实际上却是R才表示是运行状态哪为什么 会这样呢
再看一段代码就知道了 #include stdio.h#include unistd.hint main(void){while(1); {//printf(Hello process, pid: %d\n,getpid());//sleep(1);}return 0;}这样就变成了运行状态
原因就在于 printf 打印语句它是属于 IO流 的一种第一次因为是循环的缘故它一直在等IO设备就绪所以其进程状态就一直为 S对应的即是在操作系统中的【阻塞状态】但是当我们去掉 printf 这种IO流之后呢它就是在纯纯运行没有IO那也就变成了 R 状态
这里的 R 代表的就是这个进程是在前台运行的所以我们在输入任何指令后不会对其造成 任何的影响 那若是我们不以正常的方式去启动这个进程的话其进程的状态就会不一样了可以看到我在 ./mytest 的后面加上了一个 那么其状态变成了 R此代表的意思就是这个进程它是运行在了【后台】的 不过呢R状态并不代表这个进程就在运行而代表其在运行队列中排队而已.
所以总的一句话来说就是 代表是前台运行无代表后台运行后台运行时可在命令行继续输入指令并执行但无法用ctrlc结束需要用kill -9 pid杀死。想要后台运行某个程序就在后面加如./test 浅度睡眠状态
这个状态上面也提到过就上S这个状态等待io设备的输入 #include stdio.h#include unistd.hint main(){int a 0;printf(Enter# );scanf(%d, a);printf(echo : %d\n, a);return 0; } 将该进程运行起来我们可以看到其是出于 S 的状态因为【shell】此时正在等待用户的输入这个就对应到了我们上面所讲到的 阻塞状态
深度睡眠状态 除了【浅度睡眠】之外呢还有一种叫做【深度睡眠】它们俩呢都是 阻塞状态 对于浅度睡眠来说之所以称为 “浅度”是有原因的也就是处于这种状态的进程容易被唤醒。例如说我们在上面所讲到的这个处于阻塞状态的进程我们使用 kill -9 PID 向这个进程发送【9号信号】那么这个进程就被杀死了你也可以认为被唤醒了
一个进程处于深度睡眠状态disk sleep表示该进程不会被杀掉即便是操作系统也不行只有该进程自动唤醒才可以恢复。该状态有时候也叫不可中断睡眠状态uninterruptible sleep处于这个状态的进程通常会等待IO的结束。
例如某一进程要求对磁盘进行写入操作那么在磁盘进行写入期间该进程就处于深度睡眠状态是不会被杀掉的因为该进程需要等待磁盘的回复是否写入成功以做出相应的应答。磁盘休眠状态
如果在这个过程中操作系统能够杀死该进程那么就有可能丢失数据。
所以也就是因为这样操作系统不敢去打扰这个进程 暂停状态
首先我们要通过下面这句命令来查看一下对应的进程信号 kill - l 我们使用的就是1819信号 暂停进程 kill -19 PID 启动进程 kill -18 PID 那可能就有疑问了这个暂停状态和上面的睡眠状态的区别是什么呢
stopped状态 进程 完全暂停了, 其不会再接收任何信号了一个进程通过 stopped 状态可以控制另一个S 和 D 一定是在等待某种资源而 T状态 可能在等待某种资源也可能被其他进程控制 死亡状态
死亡状态只是一个返回状态当一个进程的退出信息被读取后该进程所申请的资源就会立即被释放该进程也就不存在了所以你不会在任务列表当中看到死亡状态dead。
第一种方法就是向这个进程发送9号信号就可以杀掉这个进程
第二种方法就是通过这个进程的名称来杀掉它
kill -9 PIDkillall 进程名僵尸状态
前面说到一个进程若是正在等待其退出信息被读取那么我们称该进程处于僵尸状态。而处于僵尸状态的进程我们就称之为僵尸进程。
之所以有这个状态就是因为我门需要对退出的进程进行资源的查看不能直接就退出了可能该进程里有我们需要的信息或者资源
例如对于以下代码fork函数创建的子进程在打印5次信息后会退出而父进程会一直打印信息。也就是说子进程退出了父进程还在运行但父进程没有读取子进程的退出信息那么此时子进程就进入了僵尸状态。
#include stdio.h
#include stdlib.h
#include unistd.h
int main()
{printf(I am running...\n);pid_t id fork();if(id 0){ //childint count 5;while(count){printf(I am child...PID:%d, PPID:%d, count:%d\n, getpid(), getppid(), count);sleep(1);count--;}printf(child quit...\n);exit(1);}else if(id 0){ //fatherwhile(1){printf(I am father...PID:%d, PPID:%d\n, getpid(), getppid());sleep(1);}}else{ //fork error}return 0;
} 运行该代码后我们可以通过以下监控脚本每隔一秒对该进程的信息进行检测。
while :; do ps axj | head -1 ps axj | grep proc | grep -v grep;echo ######################;sleep 1;done僵尸进程的危害 1.僵尸进程的退出状态必须一直维持下去因为它要告诉其父进程相应的退出信息。可是父进程一直不读取那么子进程也就一直处于僵尸状态。 2.僵尸进程的退出信息被保存在task_struct(PCB)中僵尸状态一直不退出那么PCB就一直需要进行维护。 3.若是一个父进程创建了很多子进程但都不进行回收那么就会造成资源浪费因为数据结构对象本身就要占用内存。 4.僵尸进程申请的资源无法进行回收那么僵尸进程越多实际可用的资源就越少也就是说僵尸进程会导致内存泄漏。 进程一般退出的时候一般其不会立即彻底退出。如果父进程没有主动回收子进程信息子进程会一直让自己处于Z状态这也是为了方便后续父进程读取子进程的相关退出结果。 孤儿状态
在Linux当中的进程关系大多数是父子关系若子进程先退出而父进程没有对子进程的退出信息进行读取那么我们称该进程为僵尸进程。但若是父进程先退出那么将来子进程进入僵尸状态时就没有父进程对其进行处理此时该子进程就称之为孤儿进程。 若是一直不处理孤儿进程的退出信息那么孤儿进程就会一直占用资源此时就会造成内存泄漏。因此当出现孤儿进程的时候孤儿进程会被1号init进程领养此后当孤儿进程进入僵尸状态时就由int进程进行处理回收。 例如对于以下代码fork函数创建的子进程会一直打印信息而父进程在打印5次信息后会退出此时该子进程就变成了孤儿进程。
同时我们一直打印子进程的PID与PPID这样便于观察现象
#include stdio.h
#include stdlib.h
#include unistd.h
int main()
{printf(I am running...\n);pid_t id fork();if(id 0){ //childint count 5;while(1){printf(I am child...PID:%d, PPID:%d\n, getpid(), getppid(), count);sleep(1);}}else if(id 0){ //fatherint count 5;while(count){printf(I am father...PID:%d, PPID:%d, count:%d\n, getpid(), getppid(), count);sleep(1);count--;}printf(father quit...\n);exit(0);}else{ //fork error}return 0;
} 四 进程优先级
基本概念
什么是优先级 优先级实际上就是获取某种资源的先后顺序而进程优先级实际上就是进程获取CPU资源分配的先后顺序就是指进程的优先权priority优先权高的进程有优先执行的权力。
优先级存在的原因 优先级存在的主要原因就是资源是有限的而存在进程优先级的主要原因就是CPU资源是有限的一个CPU一次只能跑一个进程而进程是可以有多个的所以需要存在进程优先级来确定进程获取CPU资源的先后顺序。 查看进程优先级信息 ps -al 列出的信息当中有几个重要的信息如下 UID代表执行者的身份。PID代表这个进程的代号。PPID代表这个进程是由哪个进程发展衍生而来的亦即父进程的代号。PRI代表这个进程可被执行的优先级其值越小越早被执行。NI代表这个进程的nice值。 PRI与NI 1.PRI代表进程的优先级priority通俗点说就是进程被CPU执行的先后顺序该值越小进程的优先级别越高。 2.NI代表的是nice值其表示进程可被执行的优先级的修正数值。 3.PRI值越小越快被执行当加入nice值后将会使得PRI变为PRI(new) PRI(old) NI。 4.若NI值为负值那么该进程的PRI将变小即其优先级会变高。 5.调整进程优先级在Linux下就是调整进程的nice值。 6.NI的取值范围是-20至19一共40个级别。
注意 在Linux操作系统当中PRI(old)默认为80即PRI 80 NI。
通过top命令更改进程的nice值
top命令就相当于Windows操作系统中的任务管理器它能够动态实时的显示系统当中进程的资源占用情况。 使用top命令后按“r”键会要求你输入待调整nice值的进程的PID。 输入进程PID并回车后会要求你输入调整后的nice值。
输入nice值后按“q”即可退出如果我们这里输入的nice值为10那么此时我们再用ps命令查看进程的优先级信息即可发现进程的NI变成了10PRI变成了9080NI。 注意 若是想将NI值调为负值也就是将进程的优先级调高需要使用sudo命令提升权限。 通过renice命令更改进程的nice值
使用renice命令后面跟上更改后的nice值和进程的PID即可。 四个重要概念 竞争性 系统进程数目众多而CPU资源只有少量甚至1个所以进程之间是具有竞争属性的。为了高效完成任务更合理竞争相关资源便有了优先级。 独立性 多进程运行需要独享各种资源多进程运行期间互不干扰。 并行 多个进程在多个CPU下分别同时进行运行这称之为并行。 并发 多个进程在一个CPU下采用进程切换的方式在一段时间之内让多个进程都得以推进称之为并发。 五 环境变量
基本概念
环境变量environment variables一般是指在操作系统中用来指定操作系统运行环境的一些参数。 例如我们编写的C/C代码在各个目标文件进行链接的时候从来不知道我们所链接的动静态库在哪里但是照样可以链接成功生成可执行程序原因就是有相关环境变量帮助编译器进行查找。 常见环境变量
PATH 指定命令的搜索路径。HOME 指定用户的主工作目录即用户登录到Linux系统中的默认所处目录。SHELL 当前Shell它的值通常是/bin/bash。 查看环境变量的方法
我们可以通过echo命令来查看环境变量方式如下 echo $环境变量名称 测试PATH
大家有没有想过这样一个问题为什么执行ls命令的时候不用带./就可以执行而我们自己生成的可执行程序必须要在前面带上./才可以执行 要执行一个可执行程序必须要先找到它在哪里既然不带./就可以执行ls命令说明系统能够通过ls名称找到ls的位置而系统是无法找到我们自己的可执行程序的所以我们必须带上./以此告诉系统该可执行程序位于当前目录下。 这和linux一切皆文件就是一个道理了指令也是一个可执行程序我们需要找到它才能执行它
而系统就是通过环境变量PATH来找到ls命令的查看环境变量PATH我们可以看到如下内容 上图就是默认的搜索路径
可以看到环境变量PATH当中有多条路径这些路径由冒号隔开当你使用ls命令时系统就会查看环境变量PATH然后默认从左到右依次在各个路径当中进行查找。 而ls命令实际就位于PATH当中的某一个路径下所以就算ls命令不带路径执行系统也是能够找到的。 那既然是这样我们就可以把我们写的程序的路径加上去这样默认搜索也可以搜索到我们的程序了
方式一将可执行程序拷贝到环境变量PATH的某一路径下。 sudo cp proc /usr/bin因为涉及到权限所以需要sudo命令
方式二将可执行程序所在的目录导入到环境变量PATH当中。
将可执行程序所在的目录导入到环境变量PATH当中这样一来没有指定路径时系统就会来到该目录下进行查找了。 export PATH$PATH:/home/cl/dirforproc/ENV测试HOME
任何一个用户在运行系统登录时都有自己的主工作目录家目录环境变量HOME当中即保存的该用户的主工作目录。 测试SHELL
我们在Linux操作系统当中所敲的各种命令实际上需要由命令行解释器进行解释而在Linux当中有许多种命令行解释器例如bash、sh我们可以通过查看环境变量SHELL来知道自己当前所用的命令行解释器的种类。 而该命令行解释器实际上是系统当中的一条命令当这个命令运行起来变成进程后就可以为我们进行命令行解释。 和环境变量相关的命令
1、echo显示某个环境变量的值。
2、export设置一个新的环境变量。
3、env显示所有的环境变量。 4、set显示本地定义的shell变量和环境变量。 注意:more 命令类似 cat 不过会以一页一页的形式显示更方便使用者逐页阅读而最基本的指令就是按空白键space就往下一页显示按 b 键就会往回back一页显示而且还有搜寻字串的功能与 vi 相似 5、unset清除环境变量。 环境变量的组织方式 每个程序都会收到一张环境变量表环境表是一个字符指针数组每个指针指向一个以’\0’结尾的环境字符串最后一个字符指针为空。 通过代码获取环境变量
main函数其实有三个参数只是我们平时基本不用它们所以一般情况下都没有写出来。 我们可以在Windows下的编译器进行验证当我们调试代码的时候若是一直使用逐步调试那么最终会来到调用main函数的地方。 我们可以在linux环境下编写以下代码 现在我们来说说main函数的前两个参数main函数的第二个参数是一个字符指针数组数组当中的第一个字符指针存储的是可执行程序的位置其余字符指针存储的是所给的若干选项最后一个字符指针为空而main函数的第一个参数代表的就是字符指针数组当中的有效元素个数。 main函数的第三个参数接收的实际上就是环境变量表我们可以通过main函数的第三个参数来获取系统的环境变量。 除了使用main函数的第三个参数来获取环境变量以外我们还可以通过第三方变量environ来获取。 这里的extern修饰的变量是指这个变量的的声明在其他文件中并不在当前文件和头文件中 通过系统调用获取环境变量
除了通过main函数的第三个参数和第三方变量environ来获取环境变量外我们还可以通过系统调用getenv函数来获取环境变量。 getenv函数可以根据所给环境变量名在环境变量表当中进行搜索并返回一个指向相应值的字符串指针。 例如使用getenv函数获取环境变量PATH的值。 因为是字符串指针返回所以打印的时候用%s打印就行了 六 Linux2.6内核进程调度队列 一个CPU拥有一个runqueue
如果有多个CPU就要考虑进程个数的父子均衡问题。 优先级
queue下标说明
普通优先级100~139。实时优先级0~99。
我们进程的都是普通的优先级前面说到nice值的取值范围是-20~19共40个级别依次对应queue当中普通优先级的下标100~139。
注意 实时优先级对应实时进程实时进程是指先将一个进程执行完毕再执行下一个进程现在基本不存在这种机器了所以对于queue当中下标为0~99的元素我们不关心。
也就是说这种方式的机器太慢了因为需要等一个进程完毕以后才弄下一个进程计算机的任务可是很重的这种时间的损耗对于计算机来说是很大的 活动队列
时间片还没有结束的所有进程都按照优先级放在活动队列当中其中nr_active代表总共有多少个运行状态的进程而queue[140]数组当中的一个元素就是一个进程队列相同优先级的进程按照FIFO规则进程排队调度。
调度过程如下 1.从0下标开始遍历queue[140]。 2.找到第一个非空队列该队列必定为优先级最高的队列。 3.拿到选中队列的第一个进程开始运行调度完成。 4.接着拿到选中队列的第二个进程进行调度直到选中进程队列当中的所有进程都被调度。 5.继续向后遍历queue[140]寻找下一个非空队列。
注意bitmap[5]queue数组当中一共有140个元素即140个优先级一共140个进程队列为了提高查找非空队列的效率就可以用5 × 32个比特位表示队列是否为空这样一来便可以大大提高查找效率。这里相当于位图的结构1代表有0代表没有 总结 在系统当中查找一个最合适调度的进程的时间复杂度是一个常数不会随着进程增多而导致时间成本增加我们称之为进程调度的O(1)算法。 过期队列 过期队列和活动队列的结构相同。 过期队列上放置的进程都是时间片耗尽的进程。 当活动队列上的进程被处理完毕之后对过期队列的进程进行时间片重新计算。 active指针和expired指针 active指针永远指向活动队列。 expired指针永远指向过期队列。 由于活动队列上时间片未到期的进程会越来越少而过期队列上的进程数量会越来越多新创建的进程都会被放到过期队列上那么总会出现活动队列上的全部进程的时间片都到期的情况这时将active指针和expired指针的内容交换就相当于让过期队列变成活动队列活动队列变成过期队列就相当于又具有了一批新的活动进程如此循环进行即可。