hello云胜

技术与生活

0%

Linux操作系统概览

Linux操作系统

进程

新进程的创建时通过fork一个父进程实现的。就像我们写代码,从头写太麻烦,复制一个,改吧改吧。

0号进程:系统创建的第一个进程。这是唯一一个没有通过 fork 或者 kernel_thread 产生的进程。是内核进程。但0号进程不是一个实实在在可以看到的进程

1号进程:它将运行一个用户进程。是所有用户态进程的始祖

2号进程:管理所有内核态的进程,是后面所有内核态进程的始祖

用户进程和用户进程必须进行权限分割。内核态 用户态的概念

用户进程要访问核心资源,只能通过系统调用。

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

暂停的那一刻,要把当时 CPU 的寄存器的值全部暂存到一个地方

这个过程就是这样的:用户态 - 系统调用 - 保存寄存器 - 内核态执行系统调用 - 恢复寄存器 - 返回用户态,然后接着运行。

image-20211021110125680

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 2019 ? 1-01:41:30 /sbin/init
root 2 0 0 2019 ? 00:00:00 [kthreadd]
root 3 2 0 2019 ? 00:47:34 [migration/0]
root 4 2 0 2019 ? 00:25:27 [ksoftirqd/0]
root 5 2 0 2019 ? 00:00:00 [migration/0]
root 6 2 0 2019 ? 00:01:18 [watchdog/0]
...
root 99584 99545 0 Jun03 ? 00:00:00 [sh] <defunct>
root 99587 99581 0 Apr18 ? 00:00:00 /bin/sh -c sh /home/hcsp/rsync/cron-ftp-rsync.sh > /home/hcsp/rsync/cron.out
root 99589 99587 0 Apr18 ? 00:00:00 sh /home/hcsp/rsync/cron-ftp-rsync.sh
root 99598 99589 0 Apr18 ? 00:00:00 ftp -v -n
root 99612 1 0 2020 ? 00:00:00 sshd: api [priv]
api 99617 99612 0 2020 ? 00:00:00 sshd: api@notty

PID 1 的进程就是我们的 init 进程 systemd,PID 2 的进程是内核线程 kthreadd

其中用户态的不带中括号,内核态的带中括号。

所有带中括号的内核态的进程,祖先都是 2 号进程。而用户态的进程,祖先都是 1 号进程。

每个进程都有自己独立的虚拟内存空间

进程上下文切换

上下文切换主要干两件事情,一是切换进程空间,也即虚拟内存;二是切换寄存器和 CPU 上下文。

抢占的时机

真正的抢占还需要时机,一定要规划几个时机,这个时机分为用户态和内核态。

用户态的抢占时机

线程

对于任何一个进程来讲,即便我们没有主动去创建线程,进程也是默认有一个主线程的。

线程是负责执行二进制指令的。进程要比线程管的宽多了,除了执行指令之外,内存、文件系统等等都要它来管。

所以,进程相当于一个项目,而线程就是为了完成项目需求,而建立的一个个开发任务

可不可以多进程代替多线程?

一个进程之内的线程共享内存。

进程之间内存相互隔离,进程间通信问题。


每个线程有私有的数据有两种,一是执行方法的的栈空间,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 515259
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 655350
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240 --------------栈空间大小
cpu time (seconds, -t) unlimited
max user processes (-u) 655350
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

二是线程私有数据。Thread Specific Data。这个存储时一个key,但各线程可根据自己的需要往 key 中填入不同的值,这就相当于提供了一个同名而不同值的全局变量。而等到线程退出的时候,就会调用析构函数释放 自己的value。

并发数据保护

互斥锁 Mutex

task

在 Linux 里面,无论是进程,还是线程,到了内核里面,我们统一都叫任务(Task),由一个统一的结构task_struct进行管理

image-20211021132951706

系统调用

glibc,对系统调用进行了封装。用户进程一般通过glibc进行系统调用

一旦进行系统调用,那么用户进程中断,陷入(trap)内核态

用户态函数栈

image-20211021135904588

栈基地址存的是前一个栈帧的地址,从里面拉取局部变量

内核态函数栈

内核态和用户态

image-20211021142354731

调度

进程数目远远超过 CPU 的数目,因而就需要进行进程的调度,有效地分配 CPU 的时间,既要保证进程的最快响应,也要保证进程之间的公平。这也是一个非常复杂的、需要平衡的事情。

调度策略

进程大概可以分成两种:实时进程普通进程

优先级,优先级其实就是一个数值,对于实时进程,优先级的范围是 0~99;对于普通进程,优先级的范围是 100~139。数值越小,优先级越高。从这里可以看出,所有的实时进程都比普通进程优先级要高。

普通进程使用的调度策略是 fair_sched_class 完全公平调度算法

在 Linux 里面,实现了一个基于 CFS 的调度算法。CFS 全称 Completely Fair Scheduling,叫完全公平调度

首先,你需要记录下进程的运行时间。CPU 会提供一个时钟,过一段时间就触发一个时钟中断。就像咱们的表滴答一下,这个我们叫 Tick。CFS 会为每一个进程安排一个虚拟运行时间 vruntime。如果一个进程在运行,随着时间的增长,也就是一个个 tick 的到来,进程的 vruntime 将不断增大。没有得到执行的进程 vruntime 不变。

显然,那些 vruntime 少的,原来受到了不公平的对待,需要给它补上,所以会优先运行这样的进程。

CFS 需要一个数据结构来对 vruntime 进行排序,找出最小的那个。–》能够平衡查询和更新速度的是树,在这里使用的是红黑树。

image-20211021153054847

vruntime 最小的在树的左侧,vruntime 最多的在树的右侧。 CFS 调度策略会选择红黑树最左边的叶子节点作为下一个将获得 cpu 的任务。

内存管理

虚拟地址

虚拟空间一切二,一部分用来放内核的东西,称为内核空间,一部分用来放进程的东西,称为用户空间

分段

image-20211022141151663

其实 Linux 倾向于另外一种从虚拟地址到物理地址的转换方式,称为分页(Paging)。

对于物理内存,操作系统把它分成一块一块大小相同的页,这样更方便管理,例如有的内存页面长时间不用了,可以暂时写到硬盘上,称为换出。一旦需要的时候,再加载进来,叫作换入。这样可以扩大可用物理内存的大小,提高物理内存的利用率。

这个换入和换出都是以页为单位的。页面的大小一般为 4KB。为了能够定位和访问每个页,需要有个页表,保存每个页的起始地址,再加上在页内的偏移量,组成线性地址,就能对于内存中的每个位置进行访问了。

虚拟地址分为两部分,页号页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址。这个基地址与页内偏移的组合就形成了物理内存地址。

image-20211022141525189

image-20211022141758524

虚拟内存区域可以映射到物理内存,也可以映射到文件,映射到物理内存的时候称为匿名映射,anon_vma 中,anoy 就是 anonymous,匿名的意思,映射到文件就需要有 vm_file 指定被映射的文件。

image-20211022174103451

每个 CPU 都有自己的本地内存,CPU 访问本地内存不用过总线,因而速度要快很多,每个 CPU 和内存在一起,称为一个 NUMA 节点。但是,在本地内存不足的情况下,每个 CPU 都可以去另外的 NUMA 节点申请内存,这个时候访问延时就会比较长。

每一个节点分成一个个区域 zone,zone内又分为多部份

有一块是DMA(Direct Memory Access,直接内存存取)的内存。DMA 是这样一种机制:要把外设的数据读入内存或把内存的数据传送到外设,原来都要通过 CPU 控制完成,但是这会占用 CPU,影响 CPU 处理其他事情,所以有了 DMA 模式。CPU 只需向 DMA 控制器下达指令,让 DMA 控制器来处理数据的传送,数据传送完毕再把信息反馈给 CPU,这样就可以解放 CPU。

内存映射mmap

其实内存映射不仅仅是物理内存和虚拟内存之间的映射,还包括将文件中的内容映射到虚拟内存空间。这个时候,访问内存空间就能够访问到文件里面的数据。

如果我们要申请小块内存,就用 brk。如果申请一大块内存,就要用 mmap。对于堆的申请来讲,mmap 是映射内存空间到物理内存。

另外,如果一个进程想映射一个文件到自己的虚拟内存空间,也要通过 mmap 系统调用。这个时候 mmap 是映射内存空间到物理内存再到文件。