操作系统(OS)与系统进程
- 冯诺依曼体系结构
- 操作系统(Operator System)
- 进程
- 基本概念
- 进程的描述(PCB)
- 查看进程
- 通过系统调用获取进程标示符(PID)
- 通过系统调用创建进程(fork)
- 进程状态(初识)
冯诺依曼体系结构
要去了解操作系统,冯诺依曼体系结构是必须掌握的,它决定了计算机最底层最基础的设计思路。
首先,冯诺依曼体系结构由四大部分构成,输入设备,存储器,中央处理器(CPU),输出设备。CPU里又有控制器和运算器两大器件组成。
这里的存储器指的是内存,不考虑缓存情况,这里的**CPU能且只能对内存进行读写**,不能访问外设(输入或输出设备)外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。一句话,所有设备都只能直接和内存打交道 。
因此,一个程序要运行,必须加载到内存中。原因就是冯诺依曼体系结构设计就决定了必须是这样的。
那么为什么必须是内存,CPU不能直接去外设,例如硬盘中读取数据呢,原因是存储是分级的。
木桶的短板效应很好理解,要考虑到效率问题。
操作系统(Operator System)
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括内核(进程管理,内存管理,文件管理,驱动管理),其他程序(例如函数库,shell程序等等)。
设计OS的目的 :操作系统本质就是一组软件,负责与底层硬件资源交互。与硬件交互,管理所有的软硬件资源
为用户程序(应用程序)提供一个良好的执行环境。
操作系统本质是一款负责管理的软件,管理所有的软硬件资源。
用户是不可能直接管理底层硬件资源的,因为操作系统不相信任何人,要与底层资源交互唯一的方式就是通过系统调用接口,通过操作系统去完成。
在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发 。
总结:
操作系统管理硬件方式:先描述,后组织。
描述:将要管理的对象的属性用结构体描述整合起来
组织:将一个个的结构体用特定的数据结构进行组织
进程
基本概念
课本概念:程序的一个执行实例,正在执行的程序等。
内核观点:担当分配系统资源(CPU时间,内存)的实体。
简单理解,就是加载到内存中的一个个程序,最简单直观的就是打开任务管理器,就是有很多很多进程。
概念简单理解即可。真正难理解的是进程的各种属性,以及操作系统对进程的管理。
进程的描述(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组织起来的。
查看进程
进程信息都在/proc目录下可以找到,如要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹
大多数进程信息同样可以使用top和ps这些用户级工具来获取 ,并且是比较推荐的。
例如查看全部进程。
怎么查找一个进程呢。
只需要会查找进程即可。上面一行进程的详细信息需要慢慢来理解。
通过系统调用获取进程标示符(PID)
通过系统调用可以获取自己进程的PID或者PPID,getpid()和getppid()。
通过系统调用创建进程(fork)
fork非常的神奇,它有两个返回值,没错,它是个函数,但是有两个返回值,将子进程的PID返回给父进程,子进程返回直接0。
这次你会发现一个神奇的现象,代码中写的if else结构竟然同时执行了。难道id既是0,又是大于0的数吗。
fork是怎么做到的呢,它究竟做了什么事?
Linux文档中关于fork的描述是这样的:
fork是创建一个有自己PID的子进程,但是**子进程和父进程却是共享一份代码的**。
但是我们的创建子进程的目的是为了让两个进程做不同的事情,所以需要用一定的方法将两者区别开来,所以fork采用了两个返回值的方式,但是一个id如何能表示两个值呢?
在代码执行完毕,最后要return时,父进程和子进程每个都返回一次,这才实现了fork两个返回值。
但是还有一个问题子进程和父进程却是共享一份代码,那么数据也是共享的,问题是如果要对数据做修改怎么办呢?
Linux下给出的解决方式是在子进程想要对数据进行修改的时候,操作系统会阻止,并且为子进程单独开辟一块空间将数据拷贝给子进程,这种技术叫做写时拷贝。
进程状态(初识)
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在
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):这个状态只是一个返回状态,你不会在任务列表里看到这个状态 。
进程状态有很多种,第一次接触进程状态只简单认识三大状态,运行、阻塞、挂起。
一个进程要执行要先进到运行队列中排队。
一个进程到CPU上不一定必须等到它执行结束才被拿下去,每一个进程都有时间片,如果时间到了但是没有结束依然会被拿下来到放到后面重新排队,防止一个进程一直占用资源的情况。
而由于后面的进程都在排队,但是还占据资源,为了保证进程既能正常排队,又能节省大量资源,所以只留下PCB来排队,将对应的代码和数据交换到磁盘中,等轮到时再将对应的代码和数据交换过来,中间只有PCB的过程就是一种挂起状态。