Linux 系统启动过程
1.内核引导
当计算机打开电源后,首先是BIOS开机自检,按照BIOS中设置的启动设备(通常是硬盘)来启动。
操作系统接管硬件以后,首先读入 /boot 目录下的内核文件。
2.运行init
init 进程是系统所有进程的起点,你可以把它比拟成系统所有进程的老祖宗,没有这个进程,系统中任何进程都不会启动。
init 程序首先是需要读取配置文件 /etc/inittab。
该文件是Linux系统中控制系统启动状态的主配置文件,可以使用编辑器打开它,用来查看当前系统启动级别。
init 程序的类型:
- SysV:
init,CentOS 5之前,配置文件:/etc/inittab - Upstart:
init,CentOS 6,配置文件:/etc/inittab,/etc/init/*.conf - Systemd:
systemd,CentOS 7+,配置文件:/usr/lib/systemd/system、/etc/systemd/system
3.运行级别
许多程序需要开机启动。它们在Windows叫做*“服务”(service)*,在Linux就叫做”守护进程”(daemon)。
init进程的一大任务,就是去运行这些开机启动的程序。
但是,不同的场合需要启动不同的程序,比如用作服务器时,需要启动Apache,用作桌面就不需要。
Linux允许为不同的场合,分配不同的开机启动程序,这就叫做”运行级别”(runlevel)。也就是说,启动时根据”运行级别”,确定要运行哪些程序。
Linux系统有7个运行级别(runlevel):
| 运行级别 | 描述 | 说明 |
|---|---|---|
| 0 | 系统停机状态 | 系统默认运行级别不能设为0,否则不能正常启动 |
| 1 | 单用户工作状态 | root权限,用于系统维护,禁止远程登录 |
| 2 | 多用户状态(没有NFS) | 无网络文件系统支持 |
| 3 | 完全的多用户状态(有NFS) | 登录后进入控制台命令行模式 |
| 4 | 系统未使用,保留 | 未使用,保留 |
| 5 | X11控制台,图形GUI模式 | 登录后进入图形界面 |
| 6 | 系统正常关闭并重启 | 默认运行级别不能设为6,否则不能正常启动 |
runlevel 命令可用于查看当前系统的启动级别
4.系统初始化
在init 的配置文件中有这么一行:
si::sysinit:/etc/rc.d/rc.sysinit
它调用执行了/etc/rc.d/rc.sysinit,而rc.sysinit是一个bash shell的脚本,它主要是完成一些系统初始化的工作,rc.sysinit是每一个运行级别都要首先运行的重要脚本。
它主要完成的工作有:激活交换分区,检查磁盘,加载硬件模块以及其它一些需要优先执行任务。
l5:5:wait:/etc/rc.d/rc 5
这一行表示以5为参数运行/etc/rc.d/rc,/etc/rc.d/rc是一个Shell脚本,它接受5作为参数,去执行/etc/rc.d/rc5.d/目录下的所有的rc启动脚本,/etc/rc.d/rc5.d/目录中的这些启动脚本实际上都是一些连接文件,而不是真正的rc启动脚本,真正的rc启动脚本实际上都是放在/etc/rc.d/init.d/目录下。
而这些rc启动脚本有着类似的用法,它们一般能接受start、stop、restart、status等参数。
/etc/rc.d/rc5.d/中的rc启动脚本通常是K或S开头的连接文件,对于以 S 开头的启动脚本,将以start参数来运行。
而如果发现存在相应的脚本也存在K打头的连接,而且已经处于运行态了(以/var/lock/subsys/下的文件作为标志),则将首先以stop为参数停止这些已经启动了的守护进程,然后再重新运行。
这样做是为了保证是当init改变运行级别时,所有相关的守护进程都将重启。
至于在每个运行级中将运行哪些守护进程,用户可以通过 chkconfig 或 setup 中的”System Services”来自行设定。
5.建立终端
rc执行完毕后,返回init。这时基本系统环境已经设置好了,各种守护进程也已经启动了。
init接下来会打开6个终端,以便用户登录系统。
6.用户登录系统
一般来说,用户的登录方式有三种: (1)命令行登录 (2)ssh登录 (3)图形界面登录
Linux 系统目录结构
| 目录 | 描述 | |
|---|---|---|
| /bin | bin 是 Binaries (二进制文件) 的缩写, 这个目录存放着最经常使用的命令。 | |
| /boot | 这里存放的是启动 Linux 时使用的一些核心文件,包括一些连接文件 内核文件以及镜像文件。 | |
| /dev | dev 是 Device(设备) 的缩写, 该目录下存放的是 Linux 的外部设备,在 Linux 中访问设备的方式和访问文件的方式是相同的。 | |
| /etc | etc 是 Etcetera(等等) 的缩写,这个目录用来存放所有的系统管理所需要的配置文件和子目录。/dev/null 被称空设备,是一个特殊的设备文件,它丢弃一切写入其中的数据(但报告写入操作成功),读取它则会立即得到一个 EOF。用 nohup 跑后台进程时 可以将其标准IO流重定向的这个设备丢弃数据, 避免缓冲溢出 nohup java -jar xxx.jar >/dev/null 2>&1 & | |
| /home | 用户的主目录,在 Linux 中,每个用户都有一个自己的目录,一般该目录名是以用户的账号命名的。 | |
| /lib | lib 是 Library(库) 的缩写这个目录里存放着系统最基本的动态连接共享库,其作用类似于 Windows 里的 DLL 文件。几乎所有的应用程序都需要用到这些共享库。 | |
| /lost+found | 这个目录一般情况下是空的,当系统非法关机后,这里就存放了一些文件。 使用标准的ext2/ext3档案系统格式会产生的一个 lost+found目录,目的在于当档案系统发生错误时, 将一些遗失的片段放置到这个目录下。 | |
| /media | linux 系统会自动识别一些设备,例如U盘、光驱等等,当识别后,Linux 会把识别的设备挂载到这个目录下。 | |
| /mnt | 系统提供该目录是为了让用户临时挂载别的文件系统的,我们可以将光驱挂载在 /mnt/ 上,然后进入该目录就可以查看光驱里的内容了。 | |
| /opt | opt 是 optional(可选) 的缩写,这是给主机额外安装软件所摆放的目录。比如你安装一个ORACLE数据库则就可以放到这个目录下。默认是空的。 | |
| /proc | (伪文件系统) proc 是 Processes(进程) 的缩写 存储的是当前内核运行状态的一系列特殊文件,这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息。 这个目录的内容不在硬盘上而是在内存里,我们也可以直接修改里面的某些文件,比如可以通过下面的命令来屏蔽主机的ping命令,使别人无法ping你的机器: echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all | |
| /root | 该目录为系统管理员,也称作超级权限者的用户主目录。 | |
| /sbin | s 就是 Super User 的意思,是 Superuser Binaries (超级用户的二进制文件) 的缩写,这里存放的是系统管理员使用的系统管理程序。 | |
| /selinux | 这个目录是 Redhat/CentOS 所特有的目录,Selinux 是一个安全机制,类似于 windows 的防火墙,但是这套机制比较复杂,这个目录就是存放selinux相关的文件的。 | |
| /srv | 该目录存放一些服务启动之后需要提取的数据。 | |
| /sys | (伪文件系统) Linux2.6 内核的一个很大的变化。该目录下安装了 2.6 内核中新出现的一个文件系统 sysfs 。 sysfs 文件系统集成了下面3种文件系统的信息:针对进程信息的 proc 文件系统、针对设备的 devfs 文件系统以及针对伪终端的 devpts 文件系统。 该文件系统是内核设备树的一个直观反映。当一个内核对象被创建的时候,对应的文件和目录也在内核对象子系统中被创建。 | |
| /tmp | tmp 是 temporary(临时) 的缩写这个目录是用来存放一些临时文件的。 | |
| /usr | usr 是 unix shared resources(共享资源) 的缩写,这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似于 windows 下的 program files 目录。 | |
| /usr/bin | 系统用户使用的应用程序。 | |
| /usr/sbin | 超级用户使用的比较高级的管理程序和系统守护程序。 | |
| /usr/src | 内核源代码默认的放置目录。 | |
| /var | var 是 variable(变量) 的缩写,这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件。 | |
| /run | 是一个临时文件系统,存储系统启动以来的信息。当系统重启时,这个目录下的文件应该被删掉或清除。如果你的系统上有 /var/run 目录,应该让它指向 run。 |
- 在 Linux 系统中,有几个目录是比较重要的,平时需要注意不要误删除或者随意更改内部文件。
/etc: 上边也提到了,这个是系统中的配置文件,如果你更改了该目录下的某个文件可能会导致系统不能启动。
/bin, /sbin, /usr/bin, /usr/sbin: 这是系统预设的执行文件的放置目录,比如 ls 就是在/bin/ls 目录下的。
值得提出的是,/bin, /usr/bin 是给系统用户使用的指令(除root外的通用户),而/sbin, /usr/sbin 则是给root使用的指令。
/var: 这是一个非常重要的目录,系统上跑了很多程序,那么每个程序都会有相应的日志产生,而这些日志就被记录到这个目录下,具体在/var/log 目录下,另外mail的预设放置也是在这里。
Linux 内核态 与 用户态
在硬件中 CPU 分了不同的指令级别, ring0 ~ ring3;
在 Linux 系统中只使用了 ring 0 和 ring 3 级别
-
内核态(ring 0):Linux 内核运行在 ring 0 级,拥有最高权限,可以访问所有硬件资源。 内核执行的操作:暴露 200 多个系统函数供外部调用(如
sendfile,read,write,pthread,fork…)。 -
用户态(ring 3):用户程序运行在 ring 3 级,只能访问有限的硬件资源。 用户程序不能直接访问硬件,必须通过系统调用(system call)请求内核执行操作。
Linux 进程管理
进程就是一个程序运行起来的状态,线程是一个进程中的不同的执行路径。
进程是OS分配资源的基本单位,线程是执行调度的基本单位。
分配资源最重要的是:独立的内存空间,线程调度执行(线程共享进程的内存空间,没有自己独立的内存空间)
-
进程类型: • IO密集型 大部分时间用于等待IO, 例如: 文件复制, 数据库IO • CPU密集型 大部分时间用于闷头计算, 例如: 对数据排序, 计算
-
进程优先级 • 实时进程 > 普通进程(0 - 99) • 普通进程nice值(-20 - 19)
-
时间分配 • linux采用按优先级的CPU时间比 • 其他系统多采用按优先级的时间片
Linux 进程中断
硬中断
目的: 硬件反馈给软件 中断, 例如 键盘输入、鼠标移动、网络数据包到达。
软中断 (80中断)
目的: 软件访问硬件 中断, 例如
流程
- 系统调用:int 0x80 或者 sysenter原语
- 通过ax寄存器填入调用号
- 参数通过bx cx dx si di传入内核
- 返回值通过ax返回
- java读网络 – jvm read() – c库read() - >
- 内核空间 → system_call() (系统调用处理程序)→ sys_read()
例如:Java 读网络 → JVM read() → C 库 read() → 内核空间 system_call() → sys_read()
僵尸进程
- 什么是僵尸线程
ps – ef | grep defuct
父进程产生子进程后,会维护子进程的一个PCB结构,子进程退出,由父进程释放
如果父进程没有释放(子进程退出后,父进程没有调用 wait() 或 waitpid() 收尸),那么子进程成为一个僵尸进程(大多数情况下不影响)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
int main() {
pid_t pid = fork();//fork 一个新的进程
// 下面代码会在两个进程中切换 执行?
if (0 == pid) {
printf("child id is %d\n", getpid());
printf("parent id is %d\n", getppid());
} else {
while(1) {}
}
}孤儿线程
- 什么是孤儿线程
子进程结束之前,父进程已经退出 孤儿进程会成为init进程的孩子,由1号进程维护
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
int main() {
pid_t pid = fork();
if (0 == pid) {
printf("child ppid is %d\n", getppid());
sleep(10);
printf("parent ppid is %d\n", getppid());
} else {
printf("parent id is %d\n", getpid());
sleep(5);
exit(0);
}
}
Linux 内存管理
内存分页
分页(内存不够用),内存中分成固定大小的页框(4K),把程序(硬盘上)分成4K大小的块,用到哪一块,加载那一块,加载的过程中,如果内存已经满了,会把最不常用的一块放到swap分区, 把最新的一块加载进来,这个就是著名的LRU算法
- 工作流程:
- 用到哪一块,加载那一块
- 如果内存已满,把最不常用的一块放到 swap 分区
- 把最新的一块加载进来(LRU 算法)
- LRU 算法:Least Recently Used(最不常用)
- 实现:哈希表(保证查找操作 O(1)) + 双向链表(保证排序和新增操作 O(1))
缺页中断 需要用到页面内存中没有,产生缺页异常(中断),由内核处理并加载
内存映射
虚拟内存:每个进程都虚拟的独占整个 CPU。
- 进程内部分段:段内部分页,需要该页的时候加载到页框。
- 优势:
- 隔离应用程序:每个程序都认为自己有连续可用的内存
- 突破物理内存限制:应用程序不需要考虑物理内存是否够用
- 保护物理内存:不被恶意程序访问
LINUX 内核同步数据
介绍几个关于同步的理论上的概念
• 临界区(critical area): 访问或操作共享数据的代码段 简单理解:synchronized大括号中部分(原子性)
• 竞争条件(race conditions)两个线程同时拥有临界区的执行权 • 数据不一致:data unconsistency 由竞争条件引起的数据破坏 • 同步(synchronization)避免race conditions • 锁:完成同步的手段(门锁,门后是临界区,只允许一个线程存在) 上锁解锁必须具备原子性 • 原子性(像原子一样, 最基本不可分割的操作) • 有序性(禁止指令重排) • 可见性(一个线程内的修改,另一个线程可见)
原子性 有序性 可见性 (Atomicity Ordering Visibility)
- 原子操作 – 内核中类似于AtomicXXX,位于<linux/types.h>
- 自旋锁 – 内核中通过汇编支持的cas,位于<asm/spinlock.h>
- 读-写自旋 – 类似于ReadWriteLock,可同时读,只能一个写读的时候是共享锁,写的时候是排他锁
- 信号量 – 类似于Semaphore(PV操作 down up操作 占有和释放)重量级锁,线程会进入wait,适合长时间持有的锁情况
- 读-写信号量 – downread upread downwrite upwrite(多个写,可以分段写,比较少用)(分段锁)
- 互斥体(mutex) – 特殊的信号量(二值信号量)
- 完成变量 – 特殊的信号量(A发出信号给B,B等待在完成变量上)vfork() 在子进程结束时通过完成变量叫醒父进程 类似于(Latch)
- BKL:大内核锁(早期,现在已经不用)
- 顺序锁(2.6): – 线程可以挂起的读写自旋锁 序列计数器(从0开始,写时增加(+1),写完释放(+1),读前发现单数,说明有写线程,等待,读前读后序列一样,说明没有写线程打断)
- 禁止抢占 – preempt_disable()
- 内存屏障 – 见volatile