冯诺依曼体系结构
输入单元:包括键盘, 鼠标,扫描仪, 写板等
中央处理器(CPU):含有运算器和控制器等
输出单元:显示器,打印机等
操作系统
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括内核(进程管理,内存管理,文件管理,驱动管理),其他程序(例如函数库,shell程序等等)。
在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件,它用struct结构体描述被管理对象,使用链表或者其他高效的数据结构组织被管理对象。
所以就引出来了一个问题:操作系统是怎么管理进行进程管理的呢?
答:先描述进程,在组织进程
进程
概念:程序的一个执行实例,正在执行的程序。
内核观点:担当分配系统资源(CPU时间,内存)的实体。
描述进程-PCB
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct。
task_struct---PCB的一种
在Linux中描述进程的结构体叫做task_struct。
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
task_ struct内容分类:
1.标示符: 描述本进程的唯一标示符,用来区别其他进程。
2.状态: 任务状态,退出代码,退出信号等。
3.优先级: 相对于其他进程的优先级。
4.程序计数器: 程序中即将被执行的下一条指令的地址。
5.内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
6.上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
7.I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
8.记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
9.其他信息
查看进程
进程的信息可以通过ls /proc 系统文件夹查看:
通过系统调用获取进程标示符:
通过系统调用创建进程-fork
创建出父进程和子进程
得到父进程,子进程的pid和ppid
也可以在另一个监视窗口中查看进程的状态STAT:
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
Z(zombie)-僵尸进程
僵死状态是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程,僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
现在来模拟一下僵尸进程:
#include<iostream>2 #include<sys/types.h>3 #include<unistd.h>4 using namespace std;5 6 7 int main()8 {9 // cout<<"pid:"<<getpid()<<endl;10 // cout<<"ppid:"<<getppid()<<endl;11 12 pid_t id=fork();13 14 if(id<0)15 {16 perror("fork");17 return 1;18 }19 20 else if(id==0)21 {22 int cnt=10;23 while(cnt)24 {25 cout<<"child: pid:"<<getpid()<<" ppid:"<<getppid()<<endl; 26 cnt--;27 sleep(1);28 } 29 30 }
else 33 {34 35 while(1)36 {37 cout<<"parent: pid:"<<getpid()<<" ppid:"<<getppid()<<endl;38 sleep(1);39 }40 }41 42 return 0;43 }
可以看到当子进程自己运行完毕之后,但是并没有被父进程回收,则会进入僵尸状态
僵尸进程危害
1.进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态。
2.维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护。
3.那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存。
故其会造成内存泄漏问题!
孤儿进程
父进程先退出,子进程就称之为“孤儿进程”,孤儿进程被1号init进程领养,当然要有init进程回收喽。
当父进程,子进程正常进行时:
杀掉父进程时:
子进程会自动被1进程接管,即init进程。
进程优先级
1.cpu资源分配的先后顺序,就是指进程的优先权(priority)。
2.优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
3.还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
在linux或者unix系统中,用ps –l命令则会类似输出以下几个内容:
UID : 代表执行者的身份
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值
PRI 和 NI
1.PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
2.NI就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
3.PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
4.这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
所以,调整进程优先级,在Linux下,就是调整进程nice值,nice其取值范围是-20至19,一共40个级别
需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。可以理解nice值是进程优先级的修正修正数据。
用top命令更改已存在进程的nice值
1.top
2.进入top后按“r”–>输入进程PID–>输入nice值
其他概念
1.竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
2.独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。
3.并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行。
4.并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。
环境变量
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但
是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
常见环境变量
PATH : 指定命令的搜索路径
HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL : 当前Shell,它的值通常是/bin/bash。
查看环境变量方法
echo $NAME //NAME:你的环境变量名称
和环境变量相关的命令
1. echo: 显示某个环境变量值
2. export: 设置一个新的环境变量
3. env: 显示所有环境变量
4. unset: 清除环境变量
5. set: 显示本地定义的shell变量和环境变量
通过代码如何获取环境变量
1.命令行第三个参数
2.通过第三方变量environ获取
libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。
通过系统调用获取或设置环境变量
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}
常用getenv和putenv函数来访问特定的环境变量。
也可以使用export自定义环境变量,如:export MYENV="xxxx“
程序地址空间
#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++;
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;
}
我们发现,输出出来的变量值和地址是一模一样的,但是变量内容不一样,故可以得知:
1.变量内容不一样,所以父子进程输出的变量绝对不是同一个变量。
2.地址值是一样的,说明,该地址绝对不是物理地址。
3.在Linux地址下,这种地址叫做 虚拟地址。
4.我们在用C/C++语言所看到的地址,全部都是虚拟地址。物理地址,用户一概看不到,由OS统一管理。
OS必须负责将 虚拟地址 转化成 物理地址 。
结论:同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址