操作系统 学习笔记

操作系统 学习笔记一 操作系统概述 1 1 Linux 操作系统内核体系操作系统可以比喻成一个软件外包公司 其内核就是这家公司的老板

大家好,欢迎来到IT知识分享网。

一、操作系统概述

1.1、Linux操作系统内核体系

在这里插入图片描述
操作系统可以比喻成一个软件外包公司,其内核就是这家公司的老板。

下面以“双击”为入口,逐步进入操作系统的入门。

1、输入设备–接待人员

2、输出设备–交付人员

3、中断事件

客户提需求时,客户希望外包公司把正在做的事情停下来,服务他,这时客户发送的需求被称为”中断事件“。当鼠标双击时,会触发一个中断,这相当于客户告知对接员,我有新需求,需要你给处理下。你会事先把处理这种问题的方法教给客户端对接员,这在操作系统里就是调用中断处理函数。

4、程序-项目执行计划书

对于使用,对外部公司来说,相当于接了个新业务,需要有一个项目执行计划书。内容就是,对于程序来说,他能做哪些事,每件事怎样做,先做什么后做什么,把上面的逻辑都写到程序里,这个程序就相当于项目执行计划书。

5、文件管理系统

硬盘是个物理设备,要格式化成文件系统,才能存放程序。文件系统需要一个系统统一管理,这个系统成为文件管理系统。

6、程序&进程

的二进制文件是静态的,成为程序。而运行起来的,成为进程。

7、系统调用(System Call)

当多个进程想要用打印机来打印文件,他们不能打印到同一张纸上,否则第一行是A进程输出的内容,第二行是B进程输出的内容,这样就乱了。所以,打印机的行为是由操作系统内核来统一管理的,进程不能直接操作,这个由操作系统统一管理的就是系统调用。

系统调用会列出提供了哪些接口可以调用,然后进程在有需要的时候去调用。任何一个程序要运行起来,都需要通过系统调用来创建进程。

8、进程管理系统

在操作系统中,进程的执行也需要分配CPU来执行,所以,为了管理进程,我们还需要一个进程管理系统。如果运行的进程很多,则一个CPU会并发地运行多个进程,这就需要CPU的调度能力了。

9、内存管理系统

在操作系统中,不同的进程有不同的内存空间,这些内存空间需要统一的管理和分配,这就需要内存管理系统。

1.2、系统调用相关

1.2.1、Fork

在Linux里,要创建一个新的进程,需要一个老的进程调用fork来实现,这个老的进程叫做父进程,新的进程叫做子进程。

1.2.2、进程的内存空间

在操作系统里,每个进程都有自己的内存空间,不同进程之间互不干扰。

代码段(Code segment):对于进程空间,放程序代码的部分,称为代码段。

数据段(data segment):对于进程空间,存放进程运行中产生数据的部分,称为数据段。

堆(Heap):其中局部变量部分,在当前函数运行的时候起作用,当进入另一个函数时,这个变量就释放了;也有动态分配的,会较长时间保留,指明才销毁的,这部分内存空间称为“堆”。

1.2.3、brk和mmap

1.2.4、文件管理

每个文件,Linux都会分配一个文件描述符(File Descriptor),这是一个整数。有了这个文件描述符,我们就可以使用系统调用,查看或干预进程运行的方方面面。

1.2.5、信号处理 – 项目异常处理

1.2.6、进程间通信 – 项目组间沟通

但是,当两个进程共同访问同一个内存中的数据时,就会存在竞争的问题。解决方式就是”排他“,通过信号量机制Semaphore。

Semaphore机制简介:对于只允许一个人访问的需求,可将信号量设为1。当一个人要访问时,先调用sem_wait,这时如果没有人在访问,则他就占用这个信号量,他就可以开始访问了;如果此时也有另一人要访问,也调用了sem_wait。这时后一人必须等待前一人访问完才能访问。当一人访问完成后,会调用seg_post将信号量释放,下一个等待的人就可以访问这个资源了。

1.2.7、网络通信– 公司间沟通

1.2.8、Glibc – 中介

Glibc是Linux下使用开源的标准C库,Glibc为程序员提供丰富的API,除了如字符串处理、数学运算等用户态服务外,还封装了操作系统提供的系统服务,即系统调用的封装。

1.2.9、总结

在这里插入图片描述

二、Linux核心原理:系统初始化

2.1、x86架构

计算机硬件图:

在这里插入图片描述
CPU:对计算机来讲,CPU就是大脑,他是真正干活的,所有设备的执行都围绕他展开。

总线(Bus):CPU和其他设备连接,通过总线,其实就是主板上密密麻麻的集成电路,这些组成了CPU和其他设备的高速通道。

内存:在所有设备中,最重要的是内存(Memory),因为单靠CPU是无法完成计算任务的,很多复杂的计算需要把中间结果保持起来,然后基于中间结果做进一步的计算,所以CPU要依赖于内存。

总线上还有一些其他设备,例如显卡会连接显示器,磁盘控制器会连接硬盘,USB控制器会连接键盘和鼠标等。

CPU的控制单元里面,有一个指令指针寄存器,他里面存放的是下一条指令在内存中的地址。控制单元会不停地将代码段指令拿进来,先放入指令寄存器。

当前的指令分两部分:一部分是做什么操作,如加法还是位移,一部分是操作哪些数据。

要执行这条指令,要把第一部分交个运算单元,第二部分交给数据单元。

数据单元根据数据的地址,从数据段里面读到数据寄存器里,就可以进行运算了。运算单元昨晚运算,产生的结果会暂存在数据单元的数据寄存器里。最终,会有指令将数据写回到内存中的数据段。

那么,怎样区分进程A和进程B呢?CPU里有两个寄存器,专门保持当前处理进程的代码段的起始地址,以及数据段的起始地址。这里面写的都是进程A,那当前执行的就是进程A的指令,等切换成了进程B,就会执行B的指令了,这个过程是进程切换。

CPU和内存直接传数据,靠的是总线。

2.2、从BIOS到bootloader – 创业伊始

在主板上,有一个东西叫ROM(Read Only Memory,只读存储器),和我们常说的内存RAM(Random Access Memory,随机存储器)不同。

RAM即平时买的内存条是可读可写的,而ROM是只读的,上面早就固化了一些初始化程序,也就是BIOS(Basic Input and Output System, 基本输入输出系统)。

2.3、内核初始化 – 生意做大了,成立公司

2.3.1、0号进程

第一步:创建0号进程

在操作系统里面,先要有个创始进程,有一行指令 set_task_stack_end_magic(&init_task), 这里面有一个参数init_task,他的定义是struct task_struct init_task = INIT_TASK(init_task) 。他是系统创建的第一个进程,称为0号进程。这是唯一一个没有通过fork或kernel_thread产生的进程,是进程列表的第一个。

进程列表:可以比喻成公司接的所有的项目。

2.3.2、初始化办事大厅

第二步:初始化办事大厅,这样才能响应用户的需求。

这里对应的函数是trap_init(), 里面设置了很多中断门(Interrupt Gate),用于处理各种中断。

2.3.3、内存管理初始化 – 会议室

第三步:建立会议室,对应的,mm_init()就是用来初始化内存管理模块的。

第四步:项目需要项目管理进行调度,需要执行一定的调度策略。sched_init()就是用于初始化调度模块。

2.3.4、初始化1号进程

rest_init的第一个工作是,用kernel_thread(kernel_init, NULL, CLONE_FS)创建第二个进程,这个是1号进程。

1号进程对操作系统来讲,具有划时代意义。因为他将允许一个用户进程,这意味着这个公司老板把独立完成的任务,变成了交付该他人完成的制度。这个1号进程就相当于老板的大徒弟,有了第一个,就有第二个。

如果用户态要访问核心资源,他就用通过系统调用这个办事大厅,进行请求,办事大厅后面就是内核态,用户态代码不用管后面发生了什么,只要拿到返回结果就可以了。

但一个用户态的程序运行到一半,要访问一个核心资源,例如要访问网卡发一个网络请求,就需要暂停当前的运行,调用系统调用,接下来就轮到内核中的代码运行了。

这个暂停怎么办呢?其实就是把程序运行到一半的情况保持下来。用内存来保持运行的中间结果,同时保存当前程序执行到那一行了,当前的栈在哪里,这些都是在寄存器里面的。

2.3.5、创建2号进程

内核态的所偶遇进程由2号进程来统一管理,这是系统的第三个进程。

从用户态来看,创建进程可以比喻为公司启动一个项目,项目中包含有很多资源如会议室、资料库等。这个项目需要多个人去执行,多个人并行执行不同的部分,这就是多线程。如果只有一个人,他就是这个项目的主线程。

从内核态来看,无论是进程,还是线程,我们都称为任务(task),都使用相同的数据结构,放在同一个链表中。

三、Linux核心原理:进程管理

3.1、创建进程

3.2、线程:让复杂的项目并行执行

进程可以比喻成公司接了一个项目,而线程可比喻成为了完成项目需求,而建立的一个个开发任务。

3.3、进程数据结构

3.4、调度

对操作系统来讲,他的CPU梳理是有限的,干活全靠CPU,但进程数远大于CPU数,这就需要做进程的调度,有效地分配CPU时间,既要保证进程的最快响应,也要保证进程之间的公平,这是一个非常复杂的工作。

调度时也有优先级,也有公平与非公平算法

四、内存管理

和会议室一样,内存被分层一块一块的,都编了号。例如3F-10,比如会议室3F-09,内存也有这样的地址,通过这个地址我们就能定位到物理内存的位置。

在程序里面,指令写入的地址是虚拟地址。例如,位置为10M的内存区域,操作系统会提供一种机制,将不通进程的虚拟地址和不同内存的物理地址映射起来。

当程序要访问虚拟地址的时候,由内核的数据结构进行转换,转换成不同的物理地址,这样不同的进程运行的时候,写入的是不同的物理地址,这样就不会冲突了。

在这里插入图片描述

五、文件系统

5.1、硬盘文件系统

目前Linux下最主流的文件系统格式:ext系列

类似于图书馆书架上大小相同的格子,硬盘也被分为相同大小的单元,称为块(block)。一个块的大小是扇区大小的整数倍,默认4K。在格式化时,这个值是可以设置的。

一大块硬盘被分成一个个小的block, 用来存放文件的数据,这样就不用给他分配一块连续的空间了。可以分散成一个个小块进行存放。

但这样带来的问题是,一个文件的数据存的太分散,查找起来不方便,这时,为了解决这个问题,就引入了元数据信息。元数据信息用inode来存储,inode中的 i 就是index, 每个文件会对应一个inode;一个文件夹也是一个文件,也对应一个inode。

inode中存放文件名、权限、属于哪个用户哪个组,大小多少,占多少个块。我们执行ls命令时,列出的权限、用户、大小信息,就是从inode中取出的。

5.2、文件缓存

缓存就是内存中的一块空间,因为内存比硬盘快得多。Linux为了提升性能,有时会不直接操作硬盘,读写都在内存中。

因此,文件的IO分两种类型:

1、缓存IO:大多数文件系统默认IO都是缓存IO。对于读,先看内存中有没有,有则查内存后返回;没有就去读磁盘,然后存储在缓存中。对于写,先把数据从用户空间复制到内核空间,这时对于写的用户而言,写操作就已经完成了。至于什么时候写磁盘,由操作系统确定,触发显式地调用了sync同步命令。

2、直接IO:应用程序直接访问磁盘数据,不经过内核缓冲区,从而减少了内核缓存和用户程序之间的数据复制。

六、输入输出系统

在计算机系统中,CPU并不直接和输入输出设备大角度,而是通过一个”设备控制器(Device Control Unit)的组件,例如硬盘有硬盘控制器,USB有USB控制器,显示器有视频控制器等,他们就好像是代理一样。

由于块设备传输的数据量比较大,CPU先写入缓冲区,当缓冲区中的数据攒够一定数量,才会发送给设备;CPU读取的数据,也先读入到缓冲区,也是当缓冲区的数据攒够到一定数量,才会copy到内存。

用驱动程序屏蔽设备控制器的差异

由于每种设备的控制器的寄存器、缓冲区等使用模式都不同,对操作系统来说,需要个东西来屏蔽掉这些差异。这就是用来对接各个设备控制器的设备驱动程序。

设备控制器不属于操作系统的一部分,但设备驱动程序属于操作系统。操作系统的内核代码可以向调用本地代码一样调用设备驱动程序的代码,而驱动程序的代码需要发出特殊的面向设备控制器的指令,才能操作设备控制器。

七、进程间通信

如果把进程比喻成公司的项目组,那么对于大项目,就需要多个项目组合作完成,项目组之间怎样相互通信呢?

7.1、管道

拿Linux命令举例:

ps -ef | grep 关键字 | awk '{print $2}' | xargs kill -9 
mkfifo hello 

hello就是这个管道的名称,管道以文件的形式存在,这也符合Linux一切皆文件的原则。

ls -l 

接下来,可以向管道里写数据,如:

echo "hello world" > hello 
cat < hello 

上面就是管道的方式实现的进程间的通信,可以看出,管道方式不适合进程间频繁的交换数据。所以引入了消息队列来实现进程间通信。

7.2、消息队列

消息队列不同于管道,管道是把发送的数据一次性发送,而消息队列把要发送的数据分层一个个固定大小的存储块(消息结构体),这在字节流上不连续。

使用消息队列,先定义消息结构体:

struct msg_buffer { 
    long mtype; char mtext[1024]; }; 

再使用msgget函数来创建一个消息队列,函数有一个参数key, 是消息队列的唯一标识。

具体C怎样实现消息队列,此处省略

7.3、共享内存模型

通过shmget来创建共享内存。

7.3.1、信号量

对于共享内存,两个进程同时写,怎样不互相覆盖呢?这里需要一种类似”锁“的机制,保证同一个共享资源,同一时刻只能被一个进程访问,这就是信号量(Semaphore),信号量和共享内存往往配合使用。

信号量其实是一个计数器,主要用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

我们可以把信号量初始化一个数值,来代表某种资源的总数量。然后信号量有两种原子操作,一个是申请资源操作(P操作),会将信号量的数值减去n, 表示这n个数量已被申请了,其他人不能用了;另一个是资源归还操作(V操作),表示这些资源已经归还给信号量了,其他人可以用了。

可以通过semget函数来创建一个信号量。

7.4、信号

信号是异常情况下的工作模式。Linux提供了几十种信号,分别代表不同的意义。这就行警匪片里,对于紧急行动,”1号作战任务“开始执行,就执行…来处理紧急事件。

信号可以在任何时刻发送给某一进程,进程需要为这个信号配置信号处理函数。当某个信号发生的时候,就默认执行这个函数就行了。

Signal Value Action Comment ────────────────────────────────────────────────────────────────────── SIGHUP 1 Term Hangup detected on controlling terminal or death of controlling process SIGINT 2 Term Interrupt from keyboard SIGQUIT 3 Core Quit from keyboard SIGILL 4 Core Illegal Instruction SIGABRT 6 Core Abort signal from abort(3) SIGFPE 8 Core Floating point exception SIGKILL 9 Term Kill signal SIGSEGV 11 Core Invalid memory reference SIGPIPE 13 Term Broken pipe: write to pipe with no readers SIGALRM 14 Term Timer signal from alarm(2) SIGTERM 15 Term Termination signal SIGUSR1 30,10,16 Term User-defined signal 1 SIGUSR2 31,12,17 Term User-defined signal 2 …… 

就像应急预案里给出的意义,每个信号都有一个唯一的ID,还有遇到这个信号的时候的默认操作。

7.4.1、信号的发送

八、网络系统

8.1、网络协议

一台机器将自己要表达的内容,按照某种约定好的格式发出去,另一个机器收到这些信息后,也能按照约定好的格式解析出来,从而准确可靠地获得发送方想要表达的内容,这种约定好的格式就是网络协议。

8.2、网络为什么要分层

第三层:网络层(IP层)

总而言之,第三层干的事,就是网络包从一个起始IP开始,沿着路由协议指的道儿,经过多个网络,经过多个路由转发,最终到达目标IP地址。

第二层:数据链路层(MAC层)

所谓MAC,就是每个网卡都有唯一的硬件地址(不绝对唯一,但大概率唯一,类似UUID),但这个地址没有全局定位功能。

MAC地址的定位功能局限在一个网络里,及同一个网络号下的IP地址,可通过MAC进行定位。通过IP地址获取MAC地址要通过ARP协议。

所以第二层干的事情,就是网络包在本地网络中服务器之间定位及通信的机制。

第一层:物理层

第一层就是物理设备,例如连着电脑的网线。

第四层:传输层(TCP层)

传输层有两个著名的协议:TCP和UDP。在第三层IP层的逻辑重,只负责数据从一个IP地址发送到另一个IP地址,至于丢包、乱序、重复、拥塞等,这些IP层都不管,所以处理这些逻辑的代码都放在了传输层的TCP协议里面。

我们知道,TCP是可靠的传输协议。因为第一层到第三层都不可靠,网络包说丢就丢,而TCP通过各种编号、重传等机制,让本来不可靠的网络对于上层来讲,变得“看起来”可靠。

第五层:应用层

我们常用的http, java写的servlet,都是应用层。

从第二层到第四层,都是在Linux内核里面处理的,而应用层例如浏览器、Nginx、Tomcat等都是用户态的。内核对于网络包的处理是不区分应用的。

从第四层再网上,需要区分从网络包发给哪个应用。在传输层的TCP或UDP协议里,都有端口的概念,不同应用监听不同的端口。例如nginx监听80端口,tomcat监听8080端口;再比如浏览器监听一个随机端口,FTP客户端监听另一个随机端口。

应用层和Linux内核之间的通信,是通过socket系统调用。Socket本身不属于任何一层,他只是操作系统的概念,而不是网络协议分层的概念。操作系统选择对于网络协议的实现方式是,2~4层在操作系统内核里面,而应用层的处理让应用自己去做,二者需要跨内核态和用户态通信,这就需要一个系统调用来完成这个衔接,这就是socket。

8.3、发送数据包

例如,在Linux服务器A上的客户端,打开一个浏览器连接nginx,也是通过socket, 其浏览器会被分配一个随机端口如12345。在浏览器,会将请求封装为http协议,通过socket发送给目标服务器的操作系统内核。在内核的网络协议栈里面,在TCP层创建了用于维护连接、序列号、重传、拥塞控制的数据结构:

  • 将http包上加上TCP头,发送给IP层
  • IP层加上IP头,发送给MAC层
  • MAC层加上MAC头,从硬件网卡发送出去。

网络包会先到达网络1(蓝色)的交换机,我们通常称交换机为二层设备,这是因为,交换机只会处理到第二层,然后他讲网络包的MAC头拿下来,发现目标MAC在自己右面的网口,于是就从这个网口发出去。

网络到会到达中金的Linux路由器,他左边的网口会收到网络包,发现MAC地址匹配,就交给IP层,在IP层通过IP头中的信息,在路由表中查找,下一跳在哪里,应该从哪个网口发出去。我们通常把路由器称为三层设备,因为他只会处理到第三层。

从路由器右边的网口发出去的包,会到网络2的交换机,还是会经历一次二层的处理,转发到交换机右面的网口。

最终的网络包会被转发到Linux服务器B,他发现MAC地址匹配,就将MAC头取下来,交给上一层。IP层发现IP地址匹配,将IP头取下来,交给上一层,TCP层会根据TCP头中的序列号等信息,发现他是一个正确的网络包,将网络包缓存起来,等待应用层读取。

应用层通过socket监听某个端口,因而读取的时候,操作系统内核会根据TCP头中的端口号,将网络包发给相应的应用。

http层的头和正文,是应用层来解析的。通过解析,应用层知道了客户端的请求,例如购买一个商品,还是请求一个网页,当应用层处理完http请求,会将结果仍然封装为http网络包,通过socket端口,发送给内核。

内核会经过层层封装,从物理网口发送出去,经过网络2的交换机,Linux路由器到达网络1,经过网络1的交换机,到达服务器A,在Linux服务器A上,经过层层解封,通过socket接口,根据客户端的随机端口号,发给客户端应用程序浏览器,于是,浏览器就完成了显式了。

这就是一次浏览器跨网络请求服务器,再接收返回结果做显示的过程。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/128515.html

(0)
上一篇 2025-08-30 22:33
下一篇 2025-08-30 22:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信