Linux内核工程导论——基础架构
基础功能元素
workqueue
linux下的工作队列时一种将工作推后执行的方式,其可以被睡眠、调度,与内核线程表现基本一致,但又比内核线程使用简单,一般用来处理任务内容比较动态的任务链。workqueue有个特点是自动的根据CPU不同生成不同数目的队列。每个workqueue都可以添加多个work(使用queue_work函数)。
模块支持
模块概述
可访问地址空间,可使用资源,
模块参数
用户空间通过"echo-n ${value} > /sys/module/${modulename}/parameters/${parm}" 修改模块参数。
模块的加载和卸载
动态加载
模块签名
特殊硬件框架
RAPID I/O
是一种物理连接方式,也有对应的软件驱动。用于芯片到芯片,板到板的连接,可作为嵌入式系统的背板连接。在非行业专用系统中少见。
FPGA的使用:XillyBus
Linux硬件系统中可以包含FPGA芯片,由于FPGA可被硬件随意编程为实现特定功能的组件,其实现的功能是纯硬件的,但其又要被Linux操作系统所能利用,所以就需要一个内核中存在的基础设施来驱动FPGA以导出给用户使用。
这个驱动组件就是XillyBus。XillyBus的使用者必须在FPGA中将XillyBus模块的IP核放入FPGA硬件,内核中会运行一个XillyBus的数据转发模块,导出到用户空间供用户使用。由于这是一个通用性的组件,所以无法确切的指导数据流动的特点,因此其数据采用FIFO缓存。在用户空间的设备为:/dev/xillybus_*,
$ cat mydata > /dev/xillybus_thisfifo
$ cat /dev/xillybus_thatfifo > hisdata
如此就可以读写其中的数据。
rpmsg、remoteproc
一个板子上可能有多个cpu同时在跑多个操作系统,这些操作系统可能可以共享物理内存,可能是分割的。这些板上共享内存的操作系统之间也需要通信,但是如果采用传统的socket通信,那么代价太大。内核需要一种可以让两个CPU直接访问的缓存作为通信空间,从而创造一个通信协议,这个机制叫做rpmsg。这种机制在很多上游厂商的SDK中都有类似的实现。一般的大型嵌入式系统都会自己实现一个CPU间通信的协议,但大致上都是使用内存,将一块内存划分为一块块信道。
这种板子还有一个需求,就是谁先启动的问题。一般的做法是一个这样的系统只启动一个操作系统,另外一个操作系统由先启动的操作系统启动。启动别人的操作系统叫做主操作系统系统,不但其可以控制启动,还可以控制关闭重启,远程过程调用等。这个框架功能就叫做remoteproc。
PWN
用于控制电机、LED等的通用接口。类似软甲层次的GPIO。
PIN Controller
很多硬件设备都有很多可以配置的引脚,通常是通过一系列寄存器对其进行配置和管理,而这些可用配置的种类又大致是相同的,因此就产生了抽象化的需求。这些引脚的配置空间抽象化为pin controller注册到内核的pin controll子系统中,统一管理。
VFIO、UIO
VFIO是用来取代UIO的框架,允许用户端直接访问设备细节,也就是说让用户端设备驱动成为可能。其主要的工作成果是用户端可以可以配置IOMMU,让用户端也可以编程使用DMA。不过由于是新事物,其目前还仅支持PCI设备的驱动访问(vfio-pci模块),另外对CPU的IOMMU配置,也只实现了x86和PowerPC两种。
用户端的设备文件是/dev/vfio/N。用户可以使用这个实现完全的设备驱动程序,目前的主要用途是虚拟机时的设备驱动透明访问。
SysRq
sysrq类似Windows的Ctrl+Alt+del,只要系统不是出于完全锁死的状态,都会优先响应这个命令。在Linux中这个功能本身是可以打开关闭或配置的,在/proc/sys/kernel/sysrq中。Linux中调用这系列命令的方式SysRq键+命令。SysRq在大部分键盘上一般是Print Screen按键的副功能,需要使用Alt调用。与Windows不同的是,Windows一定是在按键后跳出图形界面,而Linux允许直接使用按键命令执行特定操作:SysRq+
‘b‘:立即重启电脑
‘c‘:立即产生一个系统级的crash dump(使用NULL指针访问)
‘d‘:显示当前使用中的所有锁。
‘e‘:发送SIGTERM给出了init之外的全部进程
‘f‘:手动调用oom killer杀死一个最能用CPU的进程
‘g‘:被kgdb使用
‘h‘:显示SysRq的使用帮助
‘i‘:发送SIGKILL信号给除了init外的所有进程
‘j‘ - Forcibly "Just thaw it" - filesystems frozen by the FIFREEZEioctl.
‘k‘:杀掉当前虚拟终端上开启的所有进程
‘l‘ - Shows a stack backtrace for all active CPUs.
‘m‘:导出当前的内存信息
‘n‘ - Used to make RT tasks nice-able
‘o‘ - Will shut your system off (if configured and supported).
‘p‘ - Will dump the current registers and flags to your console.
‘q‘ - Will dump per CPU lists of all armed hrtimers (but NOT regular
timer_list timers) and detailed information about all
clockevent devices.
‘r‘ - Turns off keyboard raw mode and sets it to XLATE.
‘s‘ - Will attempt to sync all mounted filesystems.
‘t‘ - Will dump a list of current tasks and their information to your
console.
‘u‘ - Will attempt to remount all mounted filesystems read-only.
‘v‘ - Forcefully restores framebuffer console
‘v‘ - Causes ETM buffer dump [ARM-specific]
‘w‘ - Dumps tasks that are in uninterruptable (blocked) state.
‘x‘ - Used by xmon interface on ppc/powerpc platforms.
Show global PMU Registers on sparc64.
‘y‘:打印所有寄存器
‘z‘:导出ftrace buffer
‘0‘-‘9‘:设置内核的log级别
这些命令视内核的配置而部分有效。
SysCtl
时钟
高精度时钟同步:PTP
IEEE1588定义了一种新的时钟同步方式。该方式的出现是因为局域网内的高精度同步没有很好的产品。NTP和SNTP的精度不能满足需求。PTP借鉴自NTP,主要思想是通过一个同步信号周期性的与全网络中的设备同步校准。一个网络中只有一个主时钟,用来产生最高精度的信号,其他的都为边界时钟,用来接收主时钟的同步信息来调整自己。
PPS
PPS设备每一秒钟会发送一个脉冲。系统可以使用这种设备做到时钟同步,或其他定时操作。
Watchdog
/dev/watchdog
RTC
PC电脑都有一个离线还可以运行的时钟,非PC电脑可能有多个。这个时钟在运行期可以看做是准确的,实时的,但是硬件原因,长期运行产生偏差也是不可避免的。Linux内核在启动的时候会去查询这个值,并用来维护自己的时间信息,然后启动后,大部分linux都会使用网络时间来重新确定本机的时间,还会向RTC硬件写入,用来校准。完成这个工作的内核子系统叫做RTC。
由于RTC硬件的时间是存储在寄存器中的,一般存储的都是自某一个时间(1900或1970)以来的秒数,而寄存器的大小是有限的,所以不同系统对这个算法的做法就不一样。例如uboot读取这个值加上1900年就是现在的时间,但是linux除此之外会判断如果小于1969年,会加上100年得到现在的时间。由于算法不一样,所以在bootloader中和linux中看到的时钟时间不一样是正常的。
RTC子系统的存在,使得不同的硬件时钟对于系统软件透明,省去了编程的麻烦。与其他模块类似的,rtc也定义了设备,可以供用户在/dev目录下访问,叫做rtc或rtcN(n为数,一个硬件系统可能会有多个rtc时钟,但大部分PC只有一个)。大多数的rtc带有中断功能,常见的x86系统中的8号中断就是时钟中断,内核可以使用该中断功能周期性的执行自己的任务。用户端也可以通过rtc设备使用这个中断机制。打开这个设备文件后,使用ioctrl设置频率后,周期性去读取这个设备值,就能测量时间。因为设置频率就是设置了该时钟触发8号中断的频率,读取设备值得到的就是自上次读操作至今的中断数目。因此,每读一次就可以得到当前过去的时间。这个时间的粒度和准确度是可以由设置不同的频率和读取频率控制的。rtc用户端设备文件一次只允许一个用户独占的打开。
所以,如果你只是正常的使用linux时间,不需要特别精准的基于时间的中断操作,又有互联网接入,你可以不使用rtc。你也可以使用一个程序周期性去读取网络时间,通过保持同步,向外发出时间信号来做到基于时间的中断。由于系统启动后有晶振,所以一般的系统也会使用此晶振来作为时间的计量工具。因此,RTC存在的必要性在很多情况下并不大。
PADATA
并行数据处理
namaspace
magic number
引用计数组件:kref
内核中很多地方都有使用引用计数的需求。涉及到资源回收和资源竞争或者是访问统计等。这种需求一般是使用一个整数,自己编写的时候控制其增加或减少。而控制的时候又要考虑并发冲突等很多情况,通常要自己封装函数。Linux就实现了一种通用的数据结构和相关函数调用,使用者直接使用接口即可。
struct my_data
{
.
.
struct kref refcount;
.
.
};
binfmt_misc
我们在shell中敲入的命令必须是内置的或者是位于PATH变量路径中的的elf可执行文件。然而,linux不止可以支持elf格式的文件,例如通过python解释器可以执行python的程序,emac程序或者java程序等都是通过在命令行中先输入执行程序,然后键入具体要执行的命令程序。
内核提供了一种方法允许将例如java这种程序与elf一致看待。用户只需要在shell中敲入java程序名(或者python程序名),只要该程序在PATH下就可以像elf格式可执行程序一样被执行。做到这样的方式是使用binfmt_misc 机制,该机制通过proc文件系统操作,要使用首先要先mount上去:mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc。然后向/proc/sys/fs/binfmt_misc/register中写入规定格式的字符串即可。
:name:type:offset:magic:mask:interpreter:flags
具体的各个含义查看帮助。
中断系统
linux中中断分为3个层次。
最低的层次是在arch下与各个平台相关的代码,一般位于平台代码下面的irq.c文件中,该部分代码直接与硬件相关,最后都要调用do_IRQ(__do_IRQ)进行执行。
do_IRQ就是中断系统的中层,其根据下层传来的中断号找到对应的中断处理函数,处理多CPU访问和中断重入问题,然后调用真实的中断处理函数,也就是中断的上层。但是,这里内核做了区别,如果内核判断如果中断发生了嵌套(同时发生的中断多),则将中断处理函数以内核线程的形式运行,否则直接运行。所以,我们经常可以在PS命令的输出中看到:
如图这些软中断内核线程。
对于最上层,与各个中断的具体功能相关。
多CPU中断
中断亲和度
中断域
DMA系统
版权声明:本文为博主原创文章,未经博主允许不得转载。