操作系统(OS)与系统进程

冯诺依曼体系结构

要去了解操作系统,冯诺依曼体系结构是必须掌握的,它决定了计算机最底层最基础的设计思路。

image-20230903203703062

首先,冯诺依曼体系结构由四大部分构成,输入设备,存储器,中央处理器(CPU),输出设备。CPU里又有控制器和运算器两大器件组成。

image-20230903204205899

这里的存储器指的是内存,不考虑缓存情况,这里的**CPU能且只能对内存进行读写**,不能访问外设(输入或输出设备)外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。一句话,所有设备都只能直接和内存打交道 。

因此,一个程序要运行,必须加载到内存中。原因就是冯诺依曼体系结构设计就决定了必须是这样的。

那么为什么必须是内存,CPU不能直接去外设,例如硬盘中读取数据呢,原因是存储是分级的。

image-20230903204856033

木桶的短板效应很好理解,要考虑到效率问题。

扫描二维码关注公众号,回复: 16422360 查看本文章

操作系统(Operator System)

任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括内核(进程管理,内存管理,文件管理,驱动管理),其他程序(例如函数库,shell程序等等)。

设计OS的目的 :操作系统本质就是一组软件,负责与底层硬件资源交互。与硬件交互,管理所有的软硬件资源
为用户程序(应用程序)提供一个良好的执行环境

操作系统本质是一款负责管理的软件,管理所有的软硬件资源。

image-20230903210501697

用户是不可能直接管理底层硬件资源的,因为操作系统不相信任何人,要与底层资源交互唯一的方式就是通过系统调用接口,通过操作系统去完成。

在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发 。

总结:

操作系统管理硬件方式:先描述,后组织

描述:将要管理的对象的属性用结构体描述整合起来

组织:将一个个的结构体用特定的数据结构进行组织

进程

基本概念

课本概念:程序的一个执行实例,正在执行的程序等。
内核观点:担当分配系统资源(CPU时间,内存)的实体。

image-20230903211633392

简单理解,就是加载到内存中的一个个程序,最简单直观的就是打开任务管理器,就是有很多很多进程。

概念简单理解即可。真正难理解的是进程的各种属性,以及操作系统对进程的管理。

进程的描述(PCB)

一个进程通常有进程信息(PCB)以及它的数据和代码组成。

进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct

在Linux中描述进程的结构体叫做task_struct。task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。

PCB包含的信息:

标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息

组织进程的方式:可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里

Linux中是通过双链表的方式将task_struct组织起来的。

image-20230903223325072

查看进程

进程信息都在/proc目录下可以找到,如要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹

image-20230903212649190

大多数进程信息同样可以使用top和ps这些用户级工具来获取 ,并且是比较推荐的。

例如查看全部进程。

image-20230903212910050

怎么查找一个进程呢。

image-20230903213801291

只需要会查找进程即可。上面一行进程的详细信息需要慢慢来理解。

通过系统调用获取进程标示符(PID)

image-20230903214509446

通过系统调用可以获取自己进程的PID或者PPID,getpid()和getppid()。

image-20230903215353681

通过系统调用创建进程(fork)

fork非常的神奇,它有两个返回值,没错,它是个函数,但是有两个返回值,将子进程的PID返回给父进程,子进程返回直接0。

image-20230904143150246

image-20230903222924321

image-20230903222812313

这次你会发现一个神奇的现象,代码中写的if else结构竟然同时执行了。难道id既是0,又是大于0的数吗。

fork是怎么做到的呢,它究竟做了什么事?

image-20230904192212594

Linux文档中关于fork的描述是这样的:

image-20230903224205835

fork是创建一个有自己PID的子进程,但是**子进程和父进程却是共享一份代码的**。

但是我们的创建子进程的目的是为了让两个进程做不同的事情,所以需要用一定的方法将两者区别开来,所以fork采用了两个返回值的方式,但是一个id如何能表示两个值呢?

在代码执行完毕,最后要return时,父进程和子进程每个都返回一次,这才实现了fork两个返回值。

但是还有一个问题子进程和父进程却是共享一份代码,那么数据也是共享的,问题是如果要对数据做修改怎么办呢?

Linux下给出的解决方式是在子进程想要对数据进行修改的时候,操作系统会阻止,并且为子进程单独开辟一块空间将数据拷贝给子进程,这种技术叫做写时拷贝

进程状态(初识)

image-20230904191519196

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在
Linux内核里,进程有时候也叫做任务),下面是Kernel源代码中的定义:

/*
\* The task state array is a strange "bitmap" of
\* reasons to sleep. Thus "running" is zero, and
\* you can test for combinations of others with
\* simple bit tests.
*/
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 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态 。

进程状态有很多种,第一次接触进程状态只简单认识三大状态,运行、阻塞、挂起

image-20230904193840354

一个进程要执行要先进到运行队列中排队。

一个进程到CPU上不一定必须等到它执行结束才被拿下去,每一个进程都有时间片,如果时间到了但是没有结束依然会被拿下来到放到后面重新排队,防止一个进程一直占用资源的情况。

image-20230904195020472

而由于后面的进程都在排队,但是还占据资源,为了保证进程既能正常排队,又能节省大量资源,所以只留下PCB来排队,将对应的代码和数据交换到磁盘中,等轮到时再将对应的代码和数据交换过来,中间只有PCB的过程就是一种挂起状态。

image-20230904195554700

猜你喜欢

转载自blog.csdn.net/weixin_73223794/article/details/132676590