Linux初始化

Posted by Run-dream Blog on June 7, 2021

初始化

x86架构

总线

CPU

  • x86架构是指以8086为起始的intel的cpu标准,内部有 8 个 16 位的通用寄存器,20位地址总线
  • 32位处理器有 32 根地址总线。系统启动时使用实模式,终止时使用保护模式

启动

启动

  • 实模式只有1MB内存寻址空间(x86)

  • 加电,重置CS为0xFFFF,IP为0x0000,对应BIOS程序

  • 切换到保护模式需要做以下三件事

    1. 启用分段,辅助进程管理
    2. 启用分页,辅助内存管理
    3. 打开其他地址线

内核初始化

代码

创建样板进程, 及各个模块初始化

  • 创建第一个进程, 0号进程. INIT_TASK
  • 初始化中断, trap_init(). 系统调用也是通过发送中断进行, 由 set_system_intr_gate() 完成.
  • 初始化内存管理模块, mm_init()
  • 初始化进程调度模块, sched_init()
  • 初始化基于内存的文件系统 rootfs, vfs_caches_init()
  • 调用 rest_init() 完成其他初始化工作

创建管理/创建用户态进程的进程, 1号进程

  • rest_init() 通过 kernel_thread(kernel_init,...) 创建 1号进程(工作在用户态).
  • 权限管理
    • x86 提供 4个 Ring 分层权限
    • 操作系统利用: Ring0-内核态(访问核心资源); Ring3-用户态(普通程序)
  • 用户态调用系统调用: 用户态-系统调用-保存寄存器-内核态执行系统调用-恢复寄存器-返回用户态
  • 新进程执行 kernel_init 函数, 先运行 ramdisk 的 /init 程序(位于内存中)
  • 首先加载 ELF 文件 (Executable and Linkable Format,可执行与可链接格式)
  • 设置用于保存用户态寄存器的结构体
  • 返回进入用户态
  • ramdisk的 /init 加载存储设备的驱动
  • kernel_init 函数启动存储设备文件系统上的 init

创建管理/创建内核态进程的进程, 2号进程

  • rest_init() 通过 kernel_thread(kthreadd,...) 创建 2号进程(工作在内核态).
  • kthreadd 负责所有内核态线程的调度和管理

系统调用

syscall

进程管理

什么是进程

进程就是运行中的程序,在linux中有三类进程

  • init进程 用户态 不带括号
  • 内核线程 kethradd 内核态 带括号[]
  • 用户进程

tty 那一列,是问号的,说明不是前台启动的,一般都是后台的服务。

多进程缺点: 创建进程占用资源多; 进程间通信需拷贝内存, 不能共享

线程

一条线程指的是进程中一个单一顺序的控制流。

线程的数据细分成三类:

  1. 线程栈上的本地数据,每个线程都有自己的栈空间,默认大小为8M (针对于单个函数)
  2. 整个进程里共享的全局数据,可能冲突
  3. 线程私有数据

共享数据的保护机制

mutex 在共享数据访问的时候,去申请加把锁

  1. lock(没抢到则阻塞)
  2. trylock(没抢到则返回错误码);
  3. 条件变量(通知), 收到通知, 还是要抢锁(由 wait 函数执行); 因此条件变量与互斥锁配合使用

对应数据结构

内核中进程, 线程统一为任务, 由 task_struct 表示 通过链表串起 task_struct

对于一个正在运行的进程,可以通过以下命令行查看进程详细信息

ps 
cat /proc/pid

进程数据结构

  • 任务ID

    通过对比 pid 和 tgid 可判断是进程还是线程

  • 任务状态

    任务状态

  • 权限相关

    • real_cred 谁能操作此进程
    • cred 此进程能操作谁 chmod
      • uid gid 启动
      • euid egid 生效
      • fsuid fsgid 文件

    capabilities 机制

  • 内核栈

    保存用户态运行过程中的 CPU 上下文

    内核栈

    在内核态,32 位和 64 位的内核栈和 task_struct 的关联关系不同。32 位主要靠 thread_info,64 位主要靠 Per-CPU 变量。

调度

一个 CPU 上有一个队列,CFS 的队列是一棵红黑树,树的每一个节点都是一个 sched_entity,每个 sched_entity 都属于一个 task_struct,task_struct 里面有指针指向这个进程属于哪个调度类。

任务调度

设置进程和线程的调度策略

 # 查看当前进程的调度策略
 chrt -p <pid>
 # 修改当前进度的调度优先级和策略
 chrt -f -p | -i | -o <priority> <pid>
  1. 主动调度
  2. 抢占调度

主动调度

如何查看进程的运行时间和上下文切换次数

cat /proc/<pid>/sched

进程创建

fork

  1. 将 task_struct 结构复制一份并且初始化
  2. 试图唤醒新创建的子进程。

fork

线程创建

线程不是一个完全由内核实现的机制,它是由内核态和用户态合作完成的。

pthread_create 不是一个系统调用,是 Glibc 库的一个函数。

  • 内核态创建任务
  • 用户态执行线程

创建进程的话,调用的系统调用是 fork,在 copy_process 函数里面,会将五大结构 files_struct、fs_struct、sighand_struct、signal_struct、mm_struct 都复制一遍,从此父进程和子进程各用各的数据结构。

而创建线程的话,调用的是系统调用 clone,在 copy_process 函数里面, 五大结构仅仅是引用计数加一,也即线程共享进程的数据结构。

进程创建和线程创建的区别

内存管理

  • 物理内存 只有内存管理模块可以操作

  • 虚拟地址 每个进程看到的是独立的、互不干扰的虚拟地址空间

    • 内核空间 在高地址 内核 同一个内核空间
    • 用户空间 在低地址 普通进程 独占整个空间
  • 用户态内存构成

    • Text Segment 存放二进制可执行代码
    • Data Segment 存放静态常量
    • BSS Segment 未初始化的静态变量
    • 堆(Heap)段 动态分配内存
    • Memory Mapping Segment 文件映射进内存
    • 栈(Stack)地址段 函数调用的函数栈
  • 可以查看进程内存空间的布局的命令

    cat /proc/<pid>/map
    pmap <pid>
    
  • 物理内存和虚拟地址映射

    • 分段机制 Linux中段只被用于访问控制和内存保护。

    分段机制

    • 分页机制 Linux 主要使用分页的机制 多级页表

      ![分页机制](https://run-dream.github.io/img/post/linux-memry-page.webp)
      
    • 段页机制

进程空间管理

  • 32位

    32位

  • 64位

64位

直接映射区: 这一块空间是连续的,和物理内存是非常简单的映射关系

查看虚拟内存和物理内存的映射关系

cat /proc/pid/pagemap
cat /proc/kpagecount
cat /proc/kpageflags
cat /proc/kpagecgroup