学习目标
1.认识冯诺依曼系统
2.认识操作系统概念与定位 (系统调用接口)
3.理解进程的概念(PCB)
4.理解进程的状态(fork创建进程,僵尸进程及孤儿进程)
5.了解进程的调度(优先级,竞争性,独立性,并行,并发)
6.理解环境变量(熟悉常见环境变量及指令)
目录
[3.1 概念](#3.1 概念)
[3.2 PCB](#3.2 PCB)
[4.2 僵尸进程](#4.2 僵尸进程)
[4.2 孤儿进程](#4.2 孤儿进程)
1.冯诺依曼系统
- 输入单元:包括键盘, 鼠标,扫描仪, 写板等
- 中央处理器(CPU):含有运算器和控制器等
- 输出单元:显示器,打印机等
补充:
- 这里的存储器指的是内存
- 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
- 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
所有设备都只能直接和内存打交道
2.操作系统概念与定位
2.1概念
1.概念:任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
- 内核(进程管理,内存管理,文件管理,驱动管理)
- 其他程序(例如函数库,shell程序等等)
2.设计OS的的目的:
- 与硬件交互,管理所有的软硬件资源
- 为用户程序(应用程序)提供一个良好的执行环境
3.定位:
- 操作系统是一款纯正的"搞管理"的软件
4.什么是管理?
- 管理的例子
- 描述被管理对象
- 组织被管理对象
先描述,再组织
2.2系统调用与库函数
1.系统调用:
- 在开发角度,操作系统对外会表现为一个整体 ,但是会暴露自己的部分接口 ,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
2.库函数:
- 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发
3.进程的概念
3.1 概念
1.进程: 对应的代码和数据 + 进程对应的PCB结构体(包含进程的所以属性)
**--**课本概念:程序的一个执行实例,正在执行的程序等
**--**内核观点:担当分配系统资源(CPU时间,内存)的实体
3.2 PCB
1.PCB(描述进程):
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
--task_struct-PCB的一种:
- 在Linux中描述进程的结构体叫做task_struct。
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
--task_ struct内容分类:
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
2.为什么每个进程,都要有一个PCB结构体变量?
--操作系统要管理进程,先描述,再组织,
当代码加载到内存时,操作系统都为其创建一个结构体PCB,将代码和PCB关联起来,对进程的管理就变为对PCB的管理
3.3查看进程以及fork
1.进程的信息可以通过 /proc 系统文件夹查看:
- 如:要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹
- 大多数进程信息同样可以使用top和ps这些用户级工具来获取
指令:
--命令:ps ps axj 显示所有进程 ps axj | grep '进程名' (也可以看到pid) ps axj | head -1 && ps axj | grep '进程名 ' --结束进程: 1.CTRL + c 2. kill -9 pid
示例:
ps axj | grep '进程名' (也可以看到pid)
ps axj | head -1 && ps axj | grep '进程名 '
2.通过系统调用获取进程标示符:
- 进程id(PID)
- 父进程id(PPID)
代码:
头文件:sys/types.h getpid() 获取自己的进程id getppid() 获取父进程id (始终是bash相当于是shell外壳)
3.通过系统调用创建进程-fork初识
- fork有两个返回值
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
fork后变成两个进程,一个是父进程,一个是子进程
for之前是一个执行流,之后变为两个执行流
**1.头文件:**unistd.h
2.返回值:(fork具有2个返回值?)
--失败:-1
--成功:a.给父进程返回子进程的pid b.给子进程返回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){ //child printf("I am child : %d!, ret: %d\n", getpid(), ret); }else{ //father printf("I am father : %d!, ret: %d\n", getpid(), ret); } sleep(1); return 0; }
示例:
id在父进程里面,是子进程的pid
id在子进程里面,是0(这两个进程同时进行)
--为什么子进程返回0,父进程返回子进程的id?
父:子 = 1:n
--为什么fork会有两个返回值?(创建进程的时候,os要做什么?)
本质:就是系统多了一个进程,会新建一个task_struct
两个进程里面都有return语句。其会执行2次,就会有2个返回值
4.进程的状态
4.1状态
1.状态:
- --新建:字面意思
- --运行:task_struct 结构体在运行队列中排队,就叫做运行态
- --阻塞:等待非cpu资源就绪,就叫阻塞状态
- --挂起:当内存不住的时候,OS通过适当的置换进程的代码和数据到磁盘,进程的状态就叫做挂起
2.Linux下的状态:
进程:前台进程 和 后台进程(&)(不会影响命令行交互)
- R:对应运行态
- S:对应阻塞态 (可中断睡眠)
- D:睡眠状态(磁盘睡眠/深度睡眠 不可被被动唤醒)
- T:暂停状态 调试状态
- X: 终止 (死亡) 瞬时性非常强(当前资源可以被回收)
- Z:僵尸状态(一个进程已经退出,但还是不允许OS释放,处于一个被检测的状态)
--S与D的区别:
4.2 僵尸进程
1.是什么?一个进程已经退出,但还是不允许OS释放,处于一个被检测的状态
2.为什么?为了让父进程与OS来回收
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
3危害:内存泄漏(若父进程创建了多个子进程,但不回收)
4.演示:
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 5 int main() 6 { 7 pid_t id = fork(); 8 9 if(id<0) 10 { 11 printf("fail\n"); 12 } 13 else if (id == 0)//子进程 14 { 15 printf("i a child\n"); 16 sleep(5); 17 exit(-1); 18 } 19 else//父进程 20 { 21 printf("i am father\n"); 22 sleep(30); 23 } 24 return 0; 25 }
4.2 孤儿进程
1.是什么?父进程先退出,子进程就称之为"孤儿进程"
父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
孤儿进程被1号init进程领养,由init进程回收。
2.演示:
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 5 int main() 6 { 7 pid_t id = fork(); 8 9 if(id<0) 10 { 11 printf("fail\n"); 12 } 13 else if (id == 0) 14 { 15 while(1) 16 { 17 printf("i am child %d\n",getpid()); 18 sleep(1); 19 } 20 } 21 else 22 { 23 int cnt = 5; 24 while(cnt) 25 { 26 printf("i am father %d\n",getpid()); 27 cnt--; 28 sleep(1); 29 } 30 } 31 return 0; 32 } 33
5.进程的调度
5.1相关概念
优先级= 老的优先级 + nice值(80,每次设置优先级,都要从进程最开始的优先级开始设置
- cpu资源分配的先后顺序,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
- 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
5.2进程的相关信息
1.相关信息
- UID : 代表执行者的身份
- PID : 代表这个进程的代号
- PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
- PRI :代表这个进程可被执行的优先级,其值越小越早被执行
- NI :代表这个进程的nice值
如何查看?
用ps --l命令则会类似输出以下几个内容:
PRI 与 NI
- PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小,进程的优先级别越高
- 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
- PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
- 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
- 所以,调整进程优先级,在Linux下,就是调整进程nice值
- nice其取值范围是-20至19,一共40个级别
- 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化
- 可以理解nice值是进程优先级的修正修正数据
2.使用top修改进程中的nice值
top
进入top后按"r"-->输入进程PID-->输入nice值
其它概念
- 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
6.环境变量
6.1概念
1.环境变量: 一般是指在操作系统中用来指定操作系统运行环境的一些参数
--如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
--环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
2.常见的环境变量
- PATH : 指定命令的搜索路径
- HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
- SHELL : 当前Shell,它的值通常是/bin/bash
3.查看环境变量
echo $NAME //NAME:你的环境变量名称
4.测试:
5.环境变量相关指令
- 1. echo: 显示某个环境变量值
- 2. export: 设置一个新的环境变量
- 3. env: 显示所有环境变量
- 4. unset: 清除环境变量
- 5. set: 显示本地定义的shell变量和环境变量
6.2main函数中的三个参数
int main(int argc, char *argv[], char *env[]){} 参数个数 环境变量
1.环境变量的组织形式
每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以'\0'结尾的环境字符串
2.获取环境变量
--2.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; }
--通过第三方变量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声明。
--2.2通过系统调用获取或设置环境变量
//putenv //getenv #include <stdio.h> #include <stdlib.h> int main() { printf("%s\n", getenv("PATH")); return 0; }
6.2环境变量通常是具有全局属性的
--环境变量通常具有全局属性,可以被子进程继承下去
测试:
1.暂时还为设置环境变量:
2.设置后:
说明:环境变量是可以被子进程继承下去的