目录)
- 0.冯诺依曼体系结构
- 1.操作系统(Operator System)
- 1.概念
- 2.设计OS的目的
- 3.定位
- 4.系统调用和库函数概念
- 5.总结
- 2.进程
- 1.基本概念
- 2.描述进程 -- PCB
- 3.task_struct内容分类
- 4.组织进程
- 5.查看进程
- 6.通过系统调用获取进程标识符
- 7.通过系统调用创建进程 -- fork初识
- 8.进程状态
- 9.进程状态查看
- 10.僵尸进程
- 11.僵尸进程危害
- 12.孤儿进程
- 13.守护进程&精灵进程
- 3.进程优先级
- 1.基本概念
- 2.查看系统进程
- 3.PRI & NI
- 4.PRI vs NI
- 5.查看进程优先级的命令
- 6.其他概念
- 7.什么是进程切换?
0.冯诺依曼体系结构
- 截至目前,我们所认识的计算机,都是由一个个的硬件组件组成
- 输入单元:包括键盘,鼠标,扫描仪,磁盘,网卡等
- 输出单元:显示器,打印机,磁盘,网卡等
- 注意:
- 这里的存储器指的是内存
- 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
- 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取
- 一句话总结,所有设备都只能直接和内存打交道
- 对冯诺依曼的理解,不能停留在概念上,要深入到对软件数据流理解上
1.操作系统(Operator System)
1.概念
- 任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)
- 笼统的理解,操作系统包括:
- 内核(进程管理,内存管理,文件管理,驱动管理)
- 其他程序(例如函数库,shell程序等等)
2.设计OS的目的
- 与硬件交互,管理所有的软硬件资源
- 为用户程序(应用程序)提供一个良好的执行环境
3.定位
- 在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件
4.系统调用和库函数概念
- 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用
- 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发
5.总结
- 计算机管理硬件:先描述,再管理
- 描述起来,用struct结构体
- 组织起来,用链表或其他高效的数据结构
2.进程
1.基本概念
- 课本概念:程序的一个执行实例,正在执行的程序等
- 内核观点:担当分配系统资源(CPU时间,内存)的实体
- 什么叫做进程?
- 进程 = 对应的代码和数据 + 进程对应的PCB结构体
- 文件 = 内容 + 属性
2.描述进程 – PCB
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合
- PCB(Process Control Block),Linux操作系统下的PCB是: task_struct
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
3.task_struct内容分类
- **标示符:**描述本进程的唯一标示符,用来区别其他进程
- **状态:**任务状态,退出代码,退出信号等
- **优先级:**相对于其他进程的优先级
- **程序计数器:**程序中即将被执行的下一条指令的地址
- **内存指针:**包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据:进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]
- I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
- **记账信息:**可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
- 其他信息
4.组织进程
- 可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里
5.查看进程
- 进程的信息可以通过 /proc 系统文件夹查看
- 如:要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹
- 大多数进程信息同样可以使用top和ps这些用户级工具来获取
ps ajx | grep 'filename'
ps ajx | head -1 && ps ajx | grep 'filename'
6.通过系统调用获取进程标识符
- 进程id(PID) --> getpid()
- 父进程id(PPID) --> getppid()
7.通过系统调用创建进程 – fork初识
- 运行 man fork 认识fork
- fork****有两个返回值
- 为什么会有两个返回值?
- 子进程内部属性,要以父进程为模板
- fork内部,父子各自会执行自己的return语句
- 失败的时候:返回-1
- 成功的时候:
- 给父进程返回子进程的pid
- 给子进程返回0
- 为什么会有两个返回值?
- fork****之后,代码是父子共享的
- fork****之后通常要用 if 进行分流
pid_t id = fork();
if(id < 0)
{
//创建失败
perror("fork");
return 1;
}
else if(id == 0)
{
//child process(task)
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
else
{
//parent process
printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
8.进程状态
- 新建
- **运行:**task_struct结构体在运行队列中排队 --> 运行态
- **阻塞:**等待非CPU资源就绪 --> 阻塞状态
- 进程阻塞本质 --> 进程阻塞在系统函数的内部
- **挂起:**当内存不足的时候,OS通过适当的置换进程的代码和数据到磁盘,进程的状态就叫做挂起
static const char *const task_state_array[] =
{
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
- R运行状态(running):并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里
- S睡眠状态(sleeping):对应着上面的阻塞状态,意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))
- D磁盘休眠状态(Disk sleep)睡眠状态,磁盘睡眠,深度睡眠,也叫不可中断睡眠状态(uninterruptible sleep),不可以被被动唤醒,在这个状态的进程通常会等待IO的结束
- T停止状态(stopped): 可以通过发送SIGSTOP信号给进程来停止进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行
- X死亡状态(dead):这个状态只是一个返回状态,不会在任务列表里看到这个状态
9.进程状态查看
ps aux / ps axj 命令
10.僵尸进程
- 僵死状态(Zombies)是一个比较特殊的状态
- 当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵尸进程
- 僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就进入Z状态
- 综上,即一个进程已经退出,但是还不允许被OS释放,处于一个被检测的状态 --> 僵尸状态
- 以下为一个创建维持30s的僵尸进程的例子
std::cout << "I am parent process! -- PID:" << getpid() << std::endl;
pid_t ret = fork();
// 变成两个进程,一个是父进程,一个是子进程
if(ret < 0)
{
perror("fork failed");
return 1;
}
else if (ret > 0)
{
// parent process
std::cout << "parent:" << getpid() << ":is sleeping" << std::endl;
sleep(30);
}
else
{
// child process
std::cout << "child:" << getpid() << ":is sleeping" << std::endl;
sleep(5);
exit(0);
}
11.僵尸进程危害
- 进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了
- 可父进程如果一直不读取,那子进程就一直处于Z状态? --> 是的!
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中
- 换句话说,Z状态一直不退出,PCB一直都要维护? --> 是的!
- 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费? --> 是的!
- 因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置开辟空间!
- 以上是否构成内存泄漏? --> 是的!
12.孤儿进程
- 父进程先退出,子进程就称之为“孤儿进程”
- 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
- 孤儿进程被1号init进程领养,当然要由init进程回收
- 为什么要被领养?
- 未来子进程退出的时候,父进程早已不在,需要领养进程来进行回收
13.守护进程&精灵进程
- 这两种是同一种进程的不同翻译,是特殊的孤儿进程
- 不但运行在后台,最主要的是脱离了与终端和登录会话的所有联系,也就是默默的运行在后台不想受到任何影响
3.进程优先级
1.基本概念
- CPU资源分配的先后顺序,就是指进程的优先权(priority)
- 优先权高的进程有优先执行权利
- 配置进程优先权对多任务环境的Linux很有用,可以改善系统性能
- 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能
2.查看系统进程
-
在Linux或者unix系统中,用ps –l命令则会类似输出以下几个内容
-
几个重要信息:
- UID : 代表执行者的身份
- PID : 代表这个进程的代号
- PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
- PRI :代表这个进程可被执行的优先级,其值越小越早被执行
- NI :代表这个进程的nice值
3.PRI & NI
- PRI,即进程的优先级,通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
- NI,即nice值,其表示进程可被执行的优先级的修正数值
- PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
- 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
- 所以,调整进程优先级,在Linux下,就是调整进程nice值
- PRI(old)都是80,每次设置优先级,都要从进程最开始的优先级开始设置
- nice其取值范围是**-20至19**,一共40个级别
4.PRI vs NI
- 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化
- 可以理解nice值是进程优先级的修正修正数据
5.查看进程优先级的命令
- 用top命令更改已存在进程的nice:
- 进入top后按“r”–>输入进程PID–>输入nice值
6.其他概念
- 竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰,父子进程也具有独立性
- 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行
- 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
- 时间片
- 抢占与出让
7.什么是进程切换?
- OS在多任务环境下,将CPU的执行控制从一个正在执行的进程切换到另一个进程的过程
- 它确保多个进程能够共享CPU时间,并在一个系统上同时运行多个任务,使系统能够高效地利用计算资源
- 上下文切换:
- 上下文是指OS维护的关于进程状态和寄存器内容的信息
- 当OS决定切换到另一个进程时,它会保存当前执行进程的上下文,包括寄存器的内容、程序计数器和其他与进程相关的信息
- **原因:**进程切换可以由多种原因触发,包括进程的时间片耗尽、等待I/O完成、信号的到来、进程主动放弃CPU(例如,通过系统调用yield或sleep),或者新进程被调度执行
- 调度器:
- Linux使用调度器来选择下一个要运行的进程,调度器基于一些策略和优先级来选择要执行的进程
- 这确保了高优先级的任务得到更多的CPU时间,并使系统对多个任务进行公平分配
- 切换过程:
- 当发生进程切换时,操作系统会保存当前进程的上下文,选择下一个要运行的进程,然后还原其上下文,以便它从中断的地方继续执行
- 这个过程包括在内核态和用户态之间的切换,因为内核需要在不同的进程之间进行操作
- 进程切换通常包括大量的操作,如保存和恢复寄存器、切换内存映射、刷新页表等,以确保新进程能够正确执行
- 性能影响:
- 进程切换是有开销的,因为它涉及到复杂的操作。过多的进程切换会导致系统性能下降,因此操作系统的调度算法需要权衡各个进程的需求,以最大程度地减少不必要的切换