[TOC]

基础知识

Linux内核涉及进程和程序的所有算法都围绕一个名为task_struct的数据结构建立,该结构定义在/usr/include/sched.h中;task_struct数据结构提供了两个链表表头,用于实现进程家族关系;

Linux内核把虚拟地址空间划分为两个部分即核心态,用户状态,两种状态的关键差别在于对高于TASK_SIZE的内存区域的访问

Linux进程可以分为实时进程和非实时进程,硬实时进程的关键特征某些任务必须在指定的时限内完成(严格的时间限制),而软实时进程是硬实时进程的一种弱化形式。

Linux使用了源于BSD的套接字抽象,而套接字Socket可以看作应用程序、文件接口、内核的网络实现之间的代理;

Linux提供资源限制(resource limit,rlimit)机制对进程使用系统资源施加某些限制。该机制利用了task_struct中的rlimit数组项类型为struct rlimit。打开文件的数目(RLIMIT_NOFILE,默认限制在1024)。 每用户的大进程数(RLIMIT_NPROC)定义为max_threads/2。max_threads是一个全局变量,指定了在把八分之一可用内存用于管理线程信息的情况下可以创建的线程数目。在计算时提前给定了20个线程的小可能内存用量。


1.名词解释

(1)伙伴系统:系统中的空闲内存块总是两两分组,每组中的两个内存块称作伙伴

  • 伙伴的分配可以是彼此独立的但如果两个伙伴都是空闲的,内核会将其合并为一个更大的内存块,作为下一层次上某个内存块的伙伴。

(2)字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。

  • 此类设备支持按字节/字符来读写数据。举例来说调制解调器是典型的字符设备。

(3)块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。

  • 硬盘是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。
  • 此外数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。 编写块设备的驱动程序比字符设备要复杂得多,因为内核为提高系统性能广泛地使用了缓存机制。

(4)抢占式多任务处理(preemptive multitasking):各个进程都分配到一定的时间段可以执行。

  • 时间段到期后内核会从进程收回控制权,让一个不同的进程运行,而不考虑前一进程所 执行的上一个任务。
  • 被抢占进程的运行时环境,即所有CPU寄存器的内容和页表都会保存起来,因此其执行结果不会丢失。
  • 在该进程恢复执行时,其进程环境可以完全恢复,时间片的长度会根据进程执行环境决定;

(5)完全公平调度器(completely fair scheduler):在内核版本2.6.23开发期间合并进来。

  • 新的代码再 一次完全放弃了原有的设计原则;例如前一个调度器中为确保用户交互任务响应快速,需要许多启 发式原则。该调度器的关键特性是,它试图尽可能地模仿理想情况下的公平调度。
  • 此外它不仅可以调度单个进程,还能够处理更一般性的调度实体(scheduling entity);例如该调度器分配可用时间时, 可以首先在不同用户之间分配,接下来在各个用户的进程之间分配。

(6)内核抢占(kernel preemption):该选项支持在紧急情况下切换到另一个进程,甚至当前是处于核心态执行系统调用(中断处理期间是不行的)

  • 尽管内核会试图尽快执行系统调用,但对于依赖恒定数据流的应用程序来说,系统调用所需的时间仍然太长了。
  • 内核抢占可以减少这样的等待时间,因而保证“更平滑的”程序执行。但该特性的代价是 增加内核的复杂度,因为接下来有许多数据结构需要针对并发访问进行保护,即使在单处理器系统上 也是如此。
2.Inside the Linux Kernel

描述: 从下面一张图看出Linux内核之中都有啥进行简单描述:

WeiyiGeek.

WeiyiGeek.

PIPE: 是Uinx/posix中一种进程通讯机制,数据可以通过管道进行传输(实际是进程间的通讯)。
Ulimit: 是Unix/Linux中用于限制资源分配的命令,可以设置系统可以分配的文件句柄数等,例如像Apache之类的服务则需要足够的句柄数才能提供更高的连接数。

3.名称空间(Namespace)

Linux 内核中实现6种namespace说明:
| Namespace | 内容 |
| — | — |
|UTS | 主机名与域名|
|User| 用户和用户组|
|IPC | 信号量、消息队列以及共享内存|
|PID | 进程编号|
|Network|网络设备、网络栈、端口等|
|Mount| 挂载点(文件系统)|

示例1:当前Linux宿主机终端进程对应的namespace信息:

1
2
3
4
5
6
7
8
ls -l /proc/$$/ns
总用量 0
lrwxrwxrwx. 1 root root 0 7月 4 23:27 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 7月 4 23:27 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 7月 4 23:27 net -> net:[4026531956]
lrwxrwxrwx. 1 root root 0 7月 4 23:27 pid -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 7月 4 23:27 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 7月 4 23:27 uts -> uts:[4026531838]


2.Q&A

问:什么是内核线程?
答:内核线程是直接由内核本身启动的进程,它实际上是将内核函数委托给独立的进程,与系统中其他进程“并行”执行(实际上也并行于内核自身的执行), 内核线程经常称之为(内核)守护进程Deamon。它们用于执行下列任务。

  • 周期性地将修改的内存页与页来源块设备同步(例如使用mmap的文件映射)。
  • 如果内存页很少使用则写入交换区。
  • 管理延时动作(deferred action),以及我们后面讲解的Systemd。
  • 实现文件系统的事务日志。
    基本上有两种类型的内核线程:
  • 1.线程启动后一直等待,直至内核请求线程执行某一特定操作。
  • 2.线程启动后按周期性间隔运行,检测特定资源的使用,在用量超出或低于预置的限制值时采取行动。内核使用这类线程用于连续监测任务。

问:如何启动或者定义一个Linux内核函数?
答:其定义是特定于体系结构的但原型总是相同的,调用kernel_thread函数可启动一个内核线程。。

问:Linux中运行的进程如何识别那些是内核线程?
答:在ps命令的输出中很容易发现其区别,其名称都置于方括号内比如:

1
2
3
4
$ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 51780 3788 ? Ss 2019 5:05 /usr/lib/systemd/systemd --system --deserialize 20
root 2 0.0 0.0 0 0 ? S 2019 0:01 [kthreadd]

问:进程运行有几种状态?

  • 运行:该进程此刻正在执行。
  • 等待:进程能够运行但没有得到许可,因为CPU分配给另一个进程。调度器可以在下一次任务切换时选择该进程。
  • 睡眠:进程正在睡眠无法运行,因为它在等待一个外部事件。调度器无法在下一次任务切换 时选择该进程

问:僵尸进程是如何产生的?
答:僵尸进程的资源已经释放但在进程表中仍存在对应的表项,其原因在于UNIX操作系统下进程创建和销毁的方式。

  • 在两种事件发生时程序将终止运行:首先必须由另一个进程或一个用户杀死(通常是通过发送SIGTERM或SIGKILL 信号来完成,这等价于正常地终止进程);其次进程的父进程在子进程终止时必须调用或已经调用wait4 (读做wait for)系统调用。这相当于向内核证实父进程已经确认子进程的终结,该系统调用使得内核可以释放为子进程保留的资源。

  • 只有在第一个条件发生(程序终止)而第二个条件不成立的情况下(wait4),才会出现“僵尸” 状态。在进程终止之后,其数据尚未从进程表删除之前,进程总是暂时处于“僵尸”状态。有时候(例如如果父进程编程极其糟糕,没有发出wait调用),僵尸进程可能稳定地寄身于进程表中直至下一次系统重启。

问:典型的UNIX进程包括那些?
答:由二进制代码组成的应用程序、单线程(计算机沿单一路径通过代码,不会有其他路径同时运行)、分配给应用程序的一组资源(如内存、文件等)。

问:Unix多线程的实现方式?
答:有三种方式即fork 和 exec 以及 clone 方式,我们再学习Linux编程中学到的;

  • fork生成当前进程的一个相同副本,该副本称之为子进程。原进程的所有资源都以适当的方式复制到子进程,因此该系统调用之后,原来的进程就有了两个独立的实例。这两个实例的联系包括:同一组打开文件、同样的工作目录、内存中同样的数据(两个进程各有一份副本)等等此外二者别无关联。
  • exec从一个可执行的二进制文件加载另一个应用程序来代替当前运行的进程。加载了一个新程序。因为exec并不创建新进程,所以必须首先使用fork复制一个旧的程序,然后调用exec在系统上创建另一个应用程序。
  • clone的工作原理基本上与fork相同但新进程不是独立于父进程的, 而可以与其共享某些资源。写时复制,直至新进程对内存页执行写操作才会复制内存页面,这比在执行fork时盲目地立即复制所有内存页要更高效。父子进程内存页之间的联系,只有对内核才是可见的,对应用程序是透明的可以指定需要共享和复制的资源种类,例如,父进程的内存数据、打开文件或安装的信号处理程序。clone用于实现线程,但仅仅该系统调用不足以做到这一点,还需要用户空间库才能提供完整的实现。
  • 线程库的例子有Linuxthreads和Next Generation Posix Threads

内核参数

参数一览
1
2
3
4
5
6
7
8
9
10
11
sysctl -a | grep "sched_rt"


# CPU
# 内核进程CPU调度设置(RT实时、CFS完全公平调度)
kernel.sched_rt_period_us = 1000000
kernel.sched_rt_runtime_us = 950000
kernel.sched_cfs_bandwidth_slice_us = 5000

# 启用用户名称空间
kernel.unprivileged_userns_clone = 1
参数详细
  • vm.swappiness: 主要作用在内存与交换分区之间优化,该值的大小对如何使用swap分区是有着很大的联系的;
    1
    2
    3
    4
    5
    6
    7
    8
    # 默认值:60

    # 临时生效
    sysctl -w vm.swappiness=0 # 表示最大限度使用物理内存然后才再使用swap空间;
    sysctl -w vm.swappiness=100 # 表示积极的使用swap分区,并且把内存上的数据及时的搬运到swap空间里面;

    # 参数路径
    cat /proc/sys/vm/swappiness

Linux 内核源代码下载地址
官方:
https://mirrors.edge.kernel.org/pub/linux/kernel/
https://git.kernel.org/pub/

国内:
https://mirror.bjtu.edu.cn/kernel/linux/kernel/
http://mirrors.163.com/kernel/

kerner-lt # 长期支持版本; Linux内核(任何基于linux的操作系统的核心。)
kernel-ml # 主线 mainline; Linux内核(任何基于linux的操作系统的核心。)
kernel-ml-devel # 用于构建内核模块以匹配内核的开发包
kernel-ml-doc # 在内核源代码中可以找到各种文档
kernel-ml-headers # 头文件的内核,由glibc的使用
kernel-ml-tools # 用于内核的各种工具
kernel-ml-tools-libs # 内核工具的库
kernel-ml-tools-libs-devel #内核工具库的开发包

kernel-ml-4.20.13-1.el7.elrepo.x86_64
kernel-ml-4.18.1-1.el7.elrepo.x86_64

1
2
3
4
4 :目前发布的内核主版本。
18 :偶数表示稳定版本,奇数表示开发中版本。
1 :错误修补的次数。
1 :当前这个版本的第 1 次微调 patch

那么什么是动态库?为什么需要动态库?
答:所谓动态库、静态库,指的是程序编译的链接阶段,链接成可执行文件的方式

  • 静态库指的是在链接阶段将汇编生成的目标文件.o 与引用到的库一起链接打包到可执行文件中,因此对应的链接方式称为静态链接(static linking)。
  • 而动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此对应的链接方式称为动态链接(dynamic linking)。

90 年代的程序大多使用的是静态链接,因为当时的程序大多数都运行在软盘或者盒式磁带上,而且当时根本不存在标准库。这样程序在运行时与函数库再无瓜葛,移植方便。但对于 Linux 这样的分时系统,会在同一块硬盘上并发运行多个程序,这些程序基本上都会用到标准的 C 库,这时使用动态链接的优点就体现出来了。使用动态链接时,可执行文件不包含标准库文件,只包含到这些库文件的索引。例如,某程序依赖于库文件 libtrigonometry.so 中的 cos 和 sin 函数,该程序运行时就会根据索引找到并加载 libtrigonometry.so,然后程序就可以调用这个库文件中的函数。
使用动态链接的好处显而易见:
节省磁盘空间,不同的程序可以共享常见的库。
节省内存,共享的库只需从磁盘中加载到内存一次,然后在不同的程序之间共享。
更便于维护,库文件更新后,不需要重新编译使用该库的所有程序。
严格来说,动态库与共享库(shared libraries)相结合才能达到节省内存的功效。Linux 中动态库的扩展名是 .so( shared object),而 Windows 中动态库的扩展名是 .DLL(Dynamic-link library