Linux 系统启动过程

1.内核引导

当计算机打开电源后,首先是BIOS开机自检,按照BIOS中设置的启动设备(通常是硬盘)来启动。 操作系统接管硬件以后,首先读入 /boot 目录下的内核文件。

2.运行init

init 进程是系统所有进程的起点,你可以把它比拟成系统所有进程的老祖宗,没有这个进程,系统中任何进程都不会启动。

init 程序首先是需要读取配置文件 /etc/inittab。 该文件是Linux系统中控制系统启动状态的主配置文件,可以使用编辑器打开它,用来查看当前系统启动级别。

init 程序的类型:

  • SysVinit,CentOS 5之前,配置文件:/etc/inittab
  • Upstartinit,CentOS 6,配置文件:/etc/inittab/etc/init/*.conf
  • Systemdsystemd,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系统未使用,保留未使用,保留
5X11控制台,图形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改变运行级别时,所有相关的守护进程都将重启。

至于在每个运行级中将运行哪些守护进程,用户可以通过 chkconfigsetup 中的”System Services”来自行设定。

5.建立终端

rc执行完毕后,返回init。这时基本系统环境已经设置好了,各种守护进程也已经启动了。

init接下来会打开6个终端,以便用户登录系统。

6.用户登录系统

一般来说,用户的登录方式有三种: (1)命令行登录 (2)ssh登录 (3)图形界面登录

Linux 系统启动过程 - 菜鸟教程

Linux 系统目录结构

Linux 系统目录结构

目录描述
/binbin 是 Binaries (二进制文件) 的缩写, 这个目录存放着最经常使用的命令。
/boot这里存放的是启动 Linux 时使用的一些核心文件,包括一些连接文件 内核文件以及镜像文件。
/devdev 是 Device(设备) 的缩写, 该目录下存放的是 Linux 的外部设备,在 Linux 中访问设备的方式和访问文件的方式是相同的。
/etcetc 是 Etcetera(等等) 的缩写,这个目录用来存放所有的系统管理所需要的配置文件和子目录。

/dev/null 被称空设备,是一个特殊的设备文件,它丢弃一切写入其中的数据(但报告写入操作成功),读取它则会立即得到一个 EOF。

用 nohup 跑后台进程时 可以将其标准IO流重定向的这个设备丢弃数据, 避免缓冲溢出
nohup java -jar xxx.jar >/dev/null 2>&1 &
/home用户的主目录,在 Linux 中,每个用户都有一个自己的目录,一般该目录名是以用户的账号命名的。
/liblib 是 Library(库) 的缩写这个目录里存放着系统最基本的动态连接共享库,其作用类似于 Windows 里的 DLL 文件。几乎所有的应用程序都需要用到这些共享库。
/lost+found这个目录一般情况下是空的,当系统非法关机后,这里就存放了一些文件。
使用标准的ext2/ext3档案系统格式会产生的一个lost+found目录,目的在于当档案系统发生错误时, 将一些遗失的片段放置到这个目录下。
/medialinux 系统会自动识别一些设备,例如U盘、光驱等等,当识别后,Linux 会把识别的设备挂载到这个目录下。
/mnt系统提供该目录是为了让用户临时挂载别的文件系统的,我们可以将光驱挂载在 /mnt/ 上,然后进入该目录就可以查看光驱里的内容了。
/optopt 是 optional(可选) 的缩写,这是给主机额外安装软件所摆放的目录。比如你安装一个ORACLE数据库则就可以放到这个目录下。默认是空的。
/proc(伪文件系统) proc 是 Processes(进程) 的缩写 存储的是当前内核运行状态的一系列特殊文件,这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息。
这个目录的内容不在硬盘上而是在内存里,我们也可以直接修改里面的某些文件,比如可以通过下面的命令来屏蔽主机的ping命令,使别人无法ping你的机器:echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
/root该目录为系统管理员,也称作超级权限者的用户主目录。
/sbins 就是 Super User 的意思,是 Superuser Binaries (超级用户的二进制文件) 的缩写,这里存放的是系统管理员使用的系统管理程序。
/selinux这个目录是 Redhat/CentOS 所特有的目录,Selinux 是一个安全机制,类似于 windows 的防火墙,但是这套机制比较复杂,这个目录就是存放selinux相关的文件的。
/srv该目录存放一些服务启动之后需要提取的数据。
/sys(伪文件系统) Linux2.6 内核的一个很大的变化。该目录下安装了 2.6 内核中新出现的一个文件系统 sysfs 。
sysfs 文件系统集成了下面3种文件系统的信息:针对进程信息的 proc 文件系统、针对设备的 devfs 文件系统以及针对伪终端的 devpts 文件系统。
该文件系统是内核设备树的一个直观反映。当一个内核对象被创建的时候,对应的文件和目录也在内核对象子系统中被创建。
/tmptmp 是 temporary(临时) 的缩写这个目录是用来存放一些临时文件的。
/usrusr 是 unix shared resources(共享资源) 的缩写,这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似于 windows 下的 program files 目录。
/usr/bin系统用户使用的应用程序。
/usr/sbin超级用户使用的比较高级的管理程序和系统守护程序。
/usr/src内核源代码默认的放置目录。
/varvar 是 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 多个系统函数供外部调用(如 sendfilereadwritepthreadfork…)。

  • 用户态(ring 3):用户程序运行在 ring 3 级,只能访问有限的硬件资源。 用户程序不能直接访问硬件,必须通过系统调用(system call)请求内核执行操作。

Linux 进程管理

进程就是一个程序运行起来的状态,线程是一个进程中的不同的执行路径。

进程是OS分配资源的基本单位,线程是执行调度的基本单位。

分配资源最重要的是:独立的内存空间,线程调度执行(线程共享进程的内存空间,没有自己独立的内存空间)

  1. 进程类型: • IO密集型 大部分时间用于等待IO, 例如: 文件复制, 数据库IO • CPU密集型 大部分时间用于闷头计算, 例如: 对数据排序, 计算

  2. 进程优先级 • 实时进程 > 普通进程(0 - 99) • 普通进程nice值(-20 - 19)

  3. 时间分配 • linux采用按优先级的CPU时间比 • 其他系统多采用按优先级的时间片

Linux 进程中断

硬中断

目的: 硬件反馈给软件 中断, 例如 键盘输入、鼠标移动、网络数据包到达。

软中断 (80中断)

目的: 软件访问硬件 中断, 例如

流程

  1. 系统调用:int 0x80 或者 sysenter原语
  2. 通过ax寄存器填入调用号
  3. 参数通过bx cx dx si di传入内核
  4. 返回值通过ax返回
  5. java读网络 – jvm read() – c库read() - >
  6. 内核空间 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算法

  • 工作流程
    1. 用到哪一块,加载那一块
    2. 如果内存已满,把最不常用的一块放到 swap 分区
    3. 把最新的一块加载进来(LRU 算法)
  • LRU 算法:Least Recently Used(最不常用)
  • 实现:哈希表(保证查找操作 O(1)) + 双向链表(保证排序和新增操作 O(1))

缺页中断 需要用到页面内存中没有,产生缺页异常(中断),由内核处理并加载

内存映射

虚拟内存:每个进程都虚拟的独占整个 CPU。

  • 进程内部分段:段内部分页,需要该页的时候加载到页框。
  • 优势
    • 隔离应用程序:每个程序都认为自己有连续可用的内存
    • 突破物理内存限制:应用程序不需要考虑物理内存是否够用
    • 保护物理内存:不被恶意程序访问

LINUX 内核同步数据

介绍几个关于同步的理论上的概念

• 临界区(critical area): 访问或操作共享数据的代码段 简单理解:synchronized大括号中部分(原子性)

• 竞争条件(race conditions)两个线程同时拥有临界区的执行权 • 数据不一致:data unconsistency 由竞争条件引起的数据破坏 • 同步(synchronization)避免race conditions • 锁:完成同步的手段(门锁,门后是临界区,只允许一个线程存在) 上锁解锁必须具备原子性 • 原子性(像原子一样, 最基本不可分割的操作) • 有序性(禁止指令重排) • 可见性(一个线程内的修改,另一个线程可见)

原子性 有序性 可见性 (Atomicity Ordering Visibility)

  1. 原子操作 – 内核中类似于AtomicXXX,位于<linux/types.h>
  2. 自旋锁 – 内核中通过汇编支持的cas,位于<asm/spinlock.h>
  3. 读-写自旋 – 类似于ReadWriteLock,可同时读,只能一个写读的时候是共享锁,写的时候是排他锁
  4. 信号量 – 类似于Semaphore(PV操作 down up操作 占有和释放)重量级锁,线程会进入wait,适合长时间持有的锁情况
  5. 读-写信号量 – downread upread downwrite upwrite(多个写,可以分段写,比较少用)(分段锁)
  6. 互斥体(mutex) – 特殊的信号量(二值信号量)
  7. 完成变量 – 特殊的信号量(A发出信号给B,B等待在完成变量上)vfork() 在子进程结束时通过完成变量叫醒父进程 类似于(Latch)
  8. BKL:大内核锁(早期,现在已经不用)
  9. 顺序锁(2.6): – 线程可以挂起的读写自旋锁 序列计数器(从0开始,写时增加(+1),写完释放(+1),读前发现单数,说明有写线程,等待,读前读后序列一样,说明没有写线程打断)
  10. 禁止抢占 – preempt_disable()
  11. 内存屏障 – 见volatile