科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道Linux系统内核模块和用户程序比较 (2)

Linux系统内核模块和用户程序比较 (2)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

内核全权负责对硬件资源的访问,不管被访问的是显示卡,硬盘,还是内存。 用户程序常为这些资源竞争。

作者:赛迪网技术社区 来源:赛迪网技术社区 2007年10月23日

关键字: 内核 程序 模块 Linux

  • 评论
  • 分享微博
  • 分享邮件
 

用户空间和内核空间

内核全权负责对硬件资源的访问,不管被访问的是显示卡,硬盘,还是内存。 用户程序常为这些资源竞争。就如同我在保存这份文档同时本地数据库正在更新。 我的编辑器vim进程和数据库更新进程同时要求访问硬盘。内核必须使这些请求有条不紊的进行,而不是随用户的意愿提供计算机资源。 为方便实现这种机制, CPU 可以在不同的状态运行。不同的状态赋予不同的你对系统操作的自由。Intel 80836 架构有四种状态。 Unix只使用了其中 的两种,最高级的状态(操作状态0,即“超级状态”,可以执行任何操作)和最低级的状态 (即“用户状态”)。

回忆以下我们对库函数和系统调用的讨论,一般库函数在用户态执行。 库函数调用一个或几个系统调用,而这些系统调用为库函数完成工作,但是在超级状态。 一旦系统调用完成工作后系统调用就返回同时程序也返回用户态。

命名空间

如果你只是写一些短小的C程序,你可为你的变量起一个方便的和易于理解的变量名。 但是,如果你写的代码只是许多其它人写的代码的一部分,你的全局一些就会与其中的全局变量发生冲突。 另一个情况是一个程序中有太多的难以理解的变量名,这又会导致变量命名空间污染 在大型项目中,必须努力记住保留的变量名,或为独一无二的命名使用一种统一的方法。

当编写内核代码时,即使是最小的模块也会同整个内核连接,所以这的确是个令人头痛的问题。最好的解决方法是声明你的变量为static静态的并且为你的符号使用一个定义的很好的前缀。传统中,使用小写字母的内核前缀。如果你不想将所有的东西都声明为static静态的, 另一个选择是声明一个symbol table(符号表)并向内核注册。我们将在以后讨论。

文件/proc/kallsyms保存着内核知道的所有的符号,你可以访问它们, 因为它们是内核代码空间的一部分。

代码空间

内存管理是一个非常复杂的课题。O'Reilly的《Understanding The Linux Kernel》绝大部分都在 讨论内存管理!我们 并不准备专注于内存管理,但有一些东西还是得知道的。

如果你没有认真考虑过内存设计缺陷意味着什么,你也许会惊讶的获知一个指针并不指向一个确切的内存区域。当一个进程建立时,内核为它分配一部分确切的实际内存空间并把它交给进程,被进程的代码,变量,堆栈和其它一些计算机学的专家才明白的东西使用[4]。这些内存从$0$ 开始并可以扩展到需要的地方。这些内存空间并不重叠,所以即使进程访问同一个内存地址,例如0xbffff978,真实的物理内存地址其实是不同的。进程实际指向的是一块被分配的内存中以0xbffff978 为偏移量的一块内存区域。绝大多数情况下,一个进程像普通的"Hello, World"不可以访问别的进程的 内存空间,尽管有实现这种机制的方法。我们将在以后讨论。

内核自己也有内存空间。既然一个内核模块可以动态的从内核中加载和卸载,它其实是共享内核的 内存空间而不是自己拥有独立的内存空间。因此,一旦你的模块具有内存设计缺陷,内核就是内存设计缺陷了。 如果你在错误的覆盖数据,那么你就在破坏内核的代码。这比现在听起来的还糟。所以尽量小心谨慎。

顺便提一下,以上我所指出的对于任何单整体内核的操作系统都是真实的[5]。 也存在模块化微内核的操作系统,如 GNU Hurd 和 QNX Neutrino。

Device Drivers

一种内核模块是设备驱动程序,为使用硬件设备像电视卡和串口而编写。 在Unix中,任何设备都被当作路径/dev 的设备文件处理,并通过这些设备文件提供访问硬件的方法。 设备驱动为用户程序访问硬件设备。举例来说,声卡设备驱动程序es1370.o将会把设备文件 /dev/sound同声卡硬件Ensoniq IS1370联系起来。这样用户程序像 mp3blaster 就可以通过访问设备文件/dev/sound 运行而不必知道那种声卡硬件安装在系统上。

Major and Minor Numbers

让我们来研究几个设备文件。这里的几个设备文件代表着一块主IDE硬盘上的头三个分区:

# ls -l /dev/hda[1-3]
brw-rw---- 1 root disk 3, 1 Jul 5 2000 /dev/hda1
brw-rw---- 1 root disk 3, 2 Jul 5 2000 /dev/hda2
brw-rw---- 1 root disk 3, 3 Jul 5 2000 /dev/hda3

        

注意一下被逗号隔开的两列。第一个数字被叫做主设备号,第二个被叫做从设备号。 主设备号决定使用何种设备驱动程序。每种不同的设备都被分配了不同的主设备号; 所有具有相同主设备号的设备文件都是被同一个驱动程序控制。上面例子中的 主设备号都为3,表示它们都被同一个驱动程序控制。

从设备号用来区别驱动程序控制的多个设备。上面例子中的从设备号不相同是因为它们被识别为几个设备。

设备被大概的分为两类:字符设备和块设备。区别是块设备有缓冲区,所以它们可以对请求进行优化排序。 这对存储设备尤其重要,因为读写相邻的文件总比读写相隔很远的文件要快。另一个区别是块设备输入和输出 都是以数据块为单位的,但是字符设备就可以自由读写任意量的字节。大部分硬件设备为字符设备,因为它们 不需要缓冲区和数据不是按块来传输的。你可以通过命令ls -l输出的头一个字母识别一个 设备为何种设备。如果是'b' 就是块设备,如果是'c'就是字符设备。以上你看到的是块设备。这儿还有一些字符设备文件(串口):

crw-rw---- 1 root dial 4, 64 Feb 18 23:34 /dev/ttyS0
crw-r----- 1 root dial 4, 65 Nov 17 10:26 /dev/ttyS1
crw-rw---- 1 root dial 4, 66 Jul 5 2000 /dev/ttyS2
crw-rw---- 1 root dial 4, 67 Jul 5 2000 /dev/ttyS3

        

如果你想看一下已分配的主设备号都是些什么设备可以看一下文件 /usr/src/linux/Documentation/devices.txt。

系统安装时,所有的这些设备文件都是由命令mknod建立的。去建立一个新的名叫 coffee',主设备号为12和从设备号为2的设备文件,只要简单的 执行命令mknod /dev/coffee c 12 2。你并不是必须将设备文件放在目录 /dev中,这只是一个传统。Linus本人是这样做的,所以你最好也不例外。但是,当你测试一个模块时,在工作目录建立一个设备文件也不错。 只要保证完成后将它放在驱动程序找得到的地方。

我还想声明在以上讨论中隐含的几点。当系统访问一个系统文件时, 系统内核只使用主设备号来区别设备类型和决定使用何种内核模块。系统 内核并不需要知道从设备号。内核模块驱动本身才关注从设备号,并用之来 区别其操纵的不同设备。

另外,我这儿提到的硬件是比那种可以握在手里的PCI卡稍微抽象一点的东西。看一下下面的两个设备文件:

% ls -l /dev/fd0 /dev/fd0u1680
brwxrwxrwx  1 root floppy  2, 0 Jul 5 2000 /dev/fd0
brw-rw----  1 root floppy  2, 44 Jul 5 2000 /dev/fd0u1680

        

你现在立即明白这是快设备的设备文件并且它们是有相同的驱动内核模块来操纵 (主设备号都为2))。你也许也意识到它们都是你的软盘驱动器,即使你实际上只有一个软盘驱动器。为什么是两个设备文件?因为它们其中的一个代表着 你的1.44 MB容量的软驱,另一个代表着你的1.68 MB容量的,被某些人称为“超级格式化”的软驱。 这就是一个不同的从设备号代表着相同硬件设备的例子。请清楚的意识到我们提到的硬件有时可能是非常抽象的。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章