Username: Password:

Linux内核源代码漫游
来源: ChinaUnix博客  作者: ChinaUnix博客   发布时间:2008-04-28 08:32:46

Alessandro Rubini 著,
rubini@pop.systemy.it

本章试图以顺序的方式来解释Linux 源代码,以帮助读者对源代码的体系结构连同很多
相关的unix 特性的实现有一个很好的理解。目标是帮助对Linux 不甚了解的有经验的C 程
序员对整个Linux 的设计有所了解。这也就是为什么内核漫游的入点选择为内核本身的启始
点:系统引导(启动)。
这份材料需要对C 语言连同对Unix的概念和PC机的结构有很好的了解,然而本章中并
没有出现任何的C 代码,而是直接参考(指向)实际的代码的。有关内核设计的最好篇幅是
在本手册的其他章节中,而本章仍趋向于是个非正式的概述。
本章中所参阅的任何文档的路径名都是指主源代码目录树,通常是/usr/src/linux。
这里所给出的大多数信息都是取之于Linux 发行版1.0 的源代码。虽然如此,有
时也会提供对后期版本的参考。这篇漫游中开头有 图标的任何小节都是强调1.0 版本
后对内核的新的改变。假如没有这样的小节存在,则表示直到版本1.0.9-1.1.76,没有作
过改变。
有时候本章中会有象这样的小节,这是指向正确的代码以对刚讨论过的主题取得
更多信息的指示符。当然,这里是指源代码。
引导(启动)系统
当PC 的电源打开后,80x86 结构的CPU 将自动进入实模式,并从地址0xFFFF0 开始自
动执行程式代码,这个地址通常是ROM-BIOS 中的地址。PC 机的BIOS 将执行某些系统的检
测,在物理地址0 处开始初始化中断向量。此后,他将可启动设备的第一个扇区读入内存地
址0x7C00 处,并跳转到这个地方。启动设备通常是软驱或是硬盘。这里的叙述是很简单
的,但这已足够理解内核初始化的工作过程了。
Linux 的最最前面部分是用8086 汇编语言编写的(boot/bootsect.S),他将由BIOS 读
入到内存0x7C00 处,当他被执行时就会把自己移到绝对地址0x90000 处,并将启动设备
(boot/setup.S)的下2kB字节的代码读入内存0x90200 处,而内核的其他部分则被读入到地
址0x10000 处。在系统加载期间将显示信息"Loading..."。然后控制权将传递给
boot/Setup.S 中的代码,这是另一个实模式汇编语言程式。
启动部分识别主机的某些特性连同vga 卡的类型。假如需要,他会需要用户为控制台选
择显示模式。然后将整个系统从地址0x10000 移至0x1000 处,进入保护模式并跳转至系统
的余下部分(在0x1000 处)。
下一步是内核的解压缩。0x1000 处的代码来自于zBoot/head.S,他初始化寄存器并调
用decompress_kernel(),他们依次是由zBoot/inflate.c、zBoot/unzip.c和zBoot/misc.c
组成。被解压的数据存放到了地址0x10000 处(1兆),这也是为什么Linux 不能运行于少于
2 兆内存的主要原因。[在1 兆内存中解压内核的工作已完成,见 Memory Savers--ED]
将内核封装在一个gzip 文档中的工作是由zBoot 目录中的Makefile 连同工具完
成的。他们是值得一看的有趣的文档。
内核发行版1.1.75 将boot 和zBoot 目录下移到了arch/i386/boot 中了,这个改
动意味着对不同的体系结构允许真正的内核建造,但是我将仍然只讲解有关i386 的信息。
解压过的代码是从地址0x10100 处开始执行的[这里我可能忘记了具体的物理地址了,

因为我对相应的代码不是很熟],在那里,任何32 比特的配置启动被完成: IDT、GDT 连同
LDT 被加载,处理器和协处理器也已确认,分页工作也配置好了;最终调用start_kernel
子程式。上述操作的源代码是在boot/head.S 中的,这可能是整个内核中最有诀窍的代码了。
注意假如在前述任何一步中出了错,电脑就会死锁。在操作系统还没有完全运转之前
是处理不了出错的。
start_kernel()是位于init/main.c 中的,并且没有任何返回结果。从现在起的任何代
码都是用C 语言编制的,除了中断管理和系统调用的入/出代码(当然,更有大多数的宏都
嵌入了汇编代码)。
让轮子转动起来
在处理了任何错综复杂的问题之后,start_kernel()初始化了内核的任何部分,尤其是:
. 配置内存边界和调用paging_init();
. 初始化中断、IRQ 通道和调度;
. 分析(解析)命令行;
. 假如需要,就分配一个数据缓冲区(profiling buffer)连同其他一些小部分;
. 校正延迟循环(计算“BogoMips”数);
. 检查中断16是否能和协处理器工作。
最后,为了生成初始进程,内核准备好了移至move_to_user_mode(),他的代码也是在
同一个源代码文档中的。然后,所谓的空闲任务,进程号0 就进入无限的空闲循环中运行。
接着初始进程(init process)尝试着运行/etc/init、/bin/init 或/sbin/init。
假如他们没有一个运行成功的,就会去执行代码“/bin/sh /etc/rc”并且在第一个终
端上生成一个根命令解释程式(root shell)。这段代码回溯至Linux 0.01,当时操作系
统只有一个内核,并且没有登录进程。
在从一个标准的地方(让我们假定我们有)用exec()执行了init 初始化程式之后,内
核就对程式的执行没有了直接的控制。从现在起他的规则是提供对系统调用的处理,连同为
异步事件服务(比如硬件中断等)。多任务的环境已建立,从现在起是init程式通过fork()
派生出的系统进程和登录进程来管理多用户的访问了。
由于内核是负责提供服务的,这个漫游文章将通过观察这些服务(“系统调用”)连同
通过提供基本数据结构的原理和代码的组织结构继续讨论下去。
内核是怎样看见一个进程的
从内核的观点来看,一个进程只是进程表中的一个条目而已。
而进程表连同各个内存管理表和缓冲存储器则是系统中最为重要的数据结构。进程表中
的各个单项是task_struct结构,是定义在include/linux/sched.h中的很大的数据结构。
在task_struct 中保留着从低层到高层的信息,范围从某些硬件寄存器的拷贝到进程工作目
录的inode 信息。
进程表既是个数组和双链表,也是个树结构。他的物理实现是个静态的指针数组,
他的长度是定义在include/linux/tasks.h 中的常量NR_TASKS,并且每个结构都位于一个
保留内存页中。这个列表结构是通过指针next_task和pre_task 构成的,而树结构则是非
常复杂的并且我们在此将不加以讨论。您可能希望改变NR_TASKS的默认值128,但您要保
证任何源文档中相关的适当文档都要被重新编译过。
在启动引导过程结束后,内核将总是代表某个进程而工作,并且全局变量current ---
一个指向某个task_struct条目的指针 --- 被用于记录正在运行的进程。current仅能通

过在kernel/sched.c 中的调度程式来改变。然而,由于任何的进程都必须访问他,所以使
用了宏for_each_task。当系统负荷很轻时,他要比数组的顺序扫描快得多。
进程总是运行于“用户模式”或“内核模式”。用户程式的主体是运行于用户模式而其
中的系统调用则运行于内核模式中。在这两种执行模式中进程所用的堆栈是不相同的 -- 常
规的堆栈段用于用户模式,而一个固定大小的堆栈(一页,由该进程任何)则用于内核模式。
内核堆栈页是从不交换出去的,因为每当一个系统调用进入时他就必须存在着。
内核中的系统调用(system calls)是作为C 语言函数存在的,他们的‘正规’名称是
以‘sys_’开头的。例如一个名为burnout 的系统调用将调用内核函数sys_burnout()。
系统调用机制在本手册的第三章中进行了讨论。观看在include/linux/sched.h
中的for_each_task和SET_LINKS 能够帮助理解进程表中的列表和树结构。
创建和结束进程
unix 系统是通过fork()系统调用创建一个进程的,而进程的终止是通过exit()或收到
一个信号来完成的。他们的Linux实现位于kernel/fork.c和kernel/exit.c 中。 派生出
一个进程是很容易的,所以fork.c程式很短并易于理解。他的主要任务是为新的进程填写
数据结构。除了填写各个字段以外,相关的步骤有:
.. 取得一个空闲内存页面来保存task_struct
.. 找到一个空闲的进程槽(find_empty_process())
.. 为内存堆栈页kernel_stack_page 取得另一个空闲的内存页面
.. 将父辈的LDT 拷贝到子进程
.. 复制父进程的mmap 信息
sys_fork() 同样也管理文档描述符和inode。
1.0 的内核也对线程提供某些不够完善的支持,所以fork()系统调用对此也给出了
某些示意。内核的线程是主流内核以外的过程产品。
从一个进程中退出是比较有窍门的,因为父进程必须被通告有关任何子进程的退出。而
且,一个进程能够由另外一个进程使用kill()而退出(这些是Unix 的特性),所以除了
sys_exit()之外,sys_kill()连同sys_wait()的各种特性也存在于exit.c 之中了。
这里不对exit.c 的代码加以讨论---因为他一点也不令人感兴趣。为了以一致的状态退
出系统,他涉及到许多细节。而POSIX 标准对于信号则是需要相当严格的,所以这里必须对
其加以叙述。
执行程式
在调用了fork()之后,就有同一个程式的两个拷贝在运行了,通常一个程式使用exec()
执行另一个程式。exec()系统调用必须定位该执行文档的二进制映像,加载并执行他。词语
‘加载’并不一定意味着“将二进制映像拷贝进内存”,因为Linux支持按需加载。 exec()
的Linux 实现支持不同的二进制格式。这是通过linux_binfmt 结构来达到的,其中内嵌了
两个指向函数的指针--一个是用于加载可执行文档的,另一个用于加载库函数,每种二进制
格式都实现有这两个函数。共享库的加载是在exec()同一个源程式中实现的,但我们只讨
论exec()本身。 Unix 系统提供了六种exec()函数。除了一个以外,任何都是以库函数的
形式实现的,并且,Linux 内核是单独实现sys_execve()调用的。他执行一个很简单的任
务:加载可执行文档的头部,并试着去执行他。假如头两个字节是“#!”,那么就会解析该
可执行文档的第一行并调用一个解释器来执行他,否则的话,就会顺序地试用各个注册过的
二进制格式。 Linux 本身的格式是由fs/exec.c 直接支持的,并且相关的函数是
load_aout_binary 和load_aout_library。对于二进制,函数将加载一个“a.out”可执行
文档并以使用mmap()加载磁盘文档或调用read_exec()而结束。前一种方法使用了Linux
的按需加载机理,在程式被访问时使用出错加载方式(fault-in)加载程式页面,而后一种
方式是在主机文档系统不支持内存映像时(例如“msdos”文档系统)使用的。
新近的1.1内核内嵌了一个修订的msdos 文档系统,他支持mmap()。而且
linux_binfmt 结构已是个链表而不是个数组了,以允许以一个内核模块的方式加载一
个新的二进制格式。最后,结构的本身也已被扩展成能够访问和格式相关的核心转储程式
了。
访问文档系统
众所周知,文档系统是Unix 系统中最为基本的资源了,他如此的基本和普遍存在以至
于他需要一个更为便利的名字--我将忠于标准的称呼简单地称之为“fs”。
我将假设读者早已知道基本的Unix文档系统的原理--访问(权限)许可、i节点(inode)、
终极块、加载(mount)和卸载(umount)文档系统。这些概念在标准的Unix 文献中由比我聪明
的作者给出了很好的解释,所以我就不重复他们的工作并且我将只专注于有关Linux 方面的
问题。
早期的Unix通常只支持一个文档系统(fs)类型,他的代码散布于整个内核中,现今
的实现是在内核和fs之间使用一个标准的接口,以便于在不同的体系结构中进行数据的交
换。Linux 本身提供了一个标准层以在内核和每种fs模块之间传递数据。这个接口层称为
VFS,即“虚拟文档系统”("virtual filesystem")。
因而文档系统的代码被分割成了两层:上层是关于内核表格的管理和数据结构的,而低
层是由和各文档系统相关的函数集构成的,并且是由VFS数据结构进行调用的。
任何和文档系统单独的资料都位于fs/*.c 文档中。他们涉及如下的问题:
. 管理缓冲寄存器(buffer.c);
. 对fcntl()和ioctl()系统调用作出响应(fcntl.c和ioctl.c);
. 在inode 和缓冲区上映射管道和fifo(fifo.c,pipe.c);
. 管理文档 - 和inode - 表(file_table.c,inode.c);
. 锁定和解锁文档和记录(lock.c);
. 将名称映射到inode(namei.c,open.c);
. 实现错综复杂的select()函数(select.c);
. 提供信息(stat.c);
. 加载和卸载文档系统(super.c);
. 使用exec()执行可执行程式连同转储核心程式(exec.c);
. 加载各种二进制格式(bin_fmt*.c,如上面所述)。
而VFS 接口则由一组相对比较高层次的操作组成,并从和文档系统单独的代码中调用而
实际上是由每种文档系统类型执行的。最为相关的数据结构是inode_operations 和
file_operations,尽管他们不是独自存在的:同样存在着其他一些数据结构。他们都定义
在include/linux/fs.h 文档中。
到实际文档系统的内核入口点是数据结构file_system_type。file_system_types的一
个数组包含在fs/filesystems.c 中,并且每当发出了一个加载(mount)命令时都会引用他。
然后,相应fs 类型的函数read_super就负责填写结构super_block 的一个项,而该项又内
嵌了结构super_struct 和结构type_sb_info。前者为当前的fs类型提供了指向一般fs 操
作的指针,而后者对相应fs 类型内嵌了特定的信息。

文档系统类型数组已转换成了一个链表,以允许用内核模块的形式加载新的fs
类型。函数(un-)register_filesystem 代码包含在fs/super.c 中。
一个文档系统类型的快速剖析
一个文档系统类型的任务是执行用于映射相应高层VFS 操作到物理介质(磁盘、网络等
等)的低层任务。VFS 接口有足够的灵活性来支持传统的Unix 文档系统和外来的象msdos
和umsdos 文档系统类型。
每一个fs 类型除了他自己的源代码目录以外,是由下列各项组成的:
. file_systems[]数组中的一个条目(项) (fs/filesystems.c);
. 终极块(superblock)的include 文档(include/linux/type_fs_sb.h);
. i 节点(inode)的include 文档(include/linux/type_fs_i.h);
. 普通自己专用的include 文档(include/linux/type_fs.h);
. include/linux.fs.h 中的两行#include,连同在结构super_block和inode中的
条目。
对于特定fs类型自己的目录,包含有任何的实际代码、inode 和数据的管理程式。
本手册中有关procfs 的章节,揭示了任何有关那种fs 类型的低层代码和VFS 接
口。在阅读过那个章节之后,fs/procfs 中的源代码就显得很容易理解了。
现在我们来观察VFS 机制的内部工作情况,并以minix 文档系统的代码作为一个实际例
子。我选择minix类型是因为他比较短小但却是完整的;而且,Linux 中的任何其他的fs
类型都衍生于他。在最近Linux安装中的事实上的标准文档系统类型ext2,要比他复杂得
多,对ext2这个文档系统的探索就留给聪明的读者作为一个练习了。
当一个minix-fs 被加载后,minix_read_super就会把从被加载的设备中读取的数据添
入super_block 数据结构中。此时,该结构中的s_op 域将保留有一个指向minix_sops的指
针,该指针将被一般文档系统代码用于分派终极块的操作。
在全局系统树结构中链接新加载的fs依赖于下列各数据项(假设sb是终极块数据结构,
而dir_i是指向加载点的inode 的指针):
. sb->s_mounted 指向被加载文档系统的根目录i 节点(MINIX_ROOT_INO);
. dir_i->i_mount保存有sb->s_mounted;
. sb->s_covered 保存有dir_i
卸载操作将最终通过do_umount 来执行,而他会依次调用minix_put_super。
每当访问一个文档时,minix_read_inode 就会开始执行;他会使用minix_inode 各字
段中的数据填写系统范围的inode数据结构。inode->i_op 字段是依照inode->i_mode来填
写的,他将负责该文档的任何其他操作。上述minix函数的代码能够从fs/minix/inode.c
中找到。
inode_operations 数据结构是用于把inode 操作分派给特定fs类型的内核函数;该数
据结构的第一项是个指向file_operations 项的指针,他等同于数据管理的i_op。minix
文档系统类型允许有inode 操作集中的三种方式(用于目录、文档和符号链接)和文档操作
集中的两种(符号链接无需文档操作)。
目录操作(仅minix_readdir)位于fs/minix/dir.c 中;文档操作(读read 和写write)
位于fs/minix/file.c 中而符号操作(读取并跟随着链)位于fs/minix/symlink.c。
minix 源代码目录中的其余部分用于实现以下任务:
. bitmap.c 用于管理i节点和块的分配和释放(而ext2文档系统却有两个不同的代
码文档);

. fsynk.c 用于fsync()系统调用--他管理直接、间接和双重间接块(我假定您是知
道这些术语的,因为这是Unix 的普通知识);
. namei.c 内嵌有任何和名字有关的i节点的操作,比如象节点的创建和消除、重命
名和链接;
. truncate.c执行文档的截断操作。
控制台驱动程式(console driver)
作为大多数Linux 系统上的主要I/O设备,控制台驱动程式是应该受到某些关注的。有
关控制台和其他字符驱动程式的源代码能够在drivers/char中找到,当我们指称文档时,
我们将使用这个特定的目录。
控制台的初始化是由tty_io.c 中的tty_init()函数来执行的。这个函数仅仅涉及取得
每个设备集的主设备号并调用每个设备集的init函数。而con_init()则是和控制台相关的
函数,并存在于console.c 中。
在内核1.1的研发中,控制台的初始化已有了很大的变化。console_init()已
经从tty_init()中脱离出来了,并且是由../../main.c 直接调用的。现在虚拟控制台是动
态分配的,其代码也已有了很大的变化。所以我将跳过初始化、分配等等的周详讨论。
文档操作是怎样分派给控制台的
这一节是相当底层的讨论,您能够放心地跳过本节。
毫无疑问,Unix 设备是通过文档系统来访问的。本节将周详描述从设备文档到实际控
制台函数的任何步骤,而且,以下的信息是从内核的1.1.73 源代码中抽取来的,他和1.0
的代码可能少许有点不同。
当打开一个设备i 节点时,在../../fs/devices.c 中的chrdev_open()函数(或是
blkdev_open(),但我只专注于字符设备)将被执行。这个函数是通过数据结构def_chr_fops
取得的,而他又是被chrdev_inode_operations 引用的,是被任何文档系统类型使用的(见
前面有关文档系统的部分)。
chrdev_open通过在当前操作中替换具体设备的file_operations 表并且调用特定的
open()函数来管理指定的设备操作的。具体设备的表结构是保存在数组chrdevs[]中的,并
由主设备号作为索引,位于同一个../../devices.c 中。
假如该设备是个tty类型的(我们不是只关注控制台吗?),我们就来讨论tty的设
备驱动程式,他们的函数在tty_io.c 之中,由tty_fops作为索引。这样,tty_open()就会
调用init_dev(),而init_dev()就会根据次设备号为设备分配任何所需的数据结构。
次设备号也用于检索已使用tty_register_driver()注册登记过的设备的实际驱动
程式。而且,该驱动程式仍是另一个用于分派计算的数据结构,正如file_ops相同;他是
和设备的写操作和控制有关的。最后一个用于管理tty 的数据结构是线路规程,这将在后面
叙述。控制台(连同任何其他的tty 设备)的线路规程是由initialize_tty_struct()配置
的,并由init_dev调用的。
在这一节中我们所涉及的任何事情都是和设备无关的,仅有和特定控制台相关的是
console.c,在con_init()操作期间已注册了自己的驱动程式。相反,线路规程是和设备
无关的。
The tty_driver 数据结构在中有着完整的描述。
上述信息是从1.1.73 源代码中取得的。他是有可能和您的内核有所不同的(“如
信息有所变动将不另行通知”)。

控制台写操作
当往一个控制台设备进行写操作时,就会调用con_write 函数。这个函数管理任何控制
字符和换码字符序列,这些字符给应用程式提供全部的屏幕管理操作。所实现的换码序列是
vt102 终端的;这意味着当您使用telnet 连接到一台非Linux主机时,您的环境变量应该
有TERM=vt102;然而,对于本地操作最好的选择是配置TERM=console,因为Linux 控制台
提供了一个vt102 功能的超集。
因而,con_write()主要是由转换语句组成的,用于处理每一次一个字符的有限长状态
自动换码序列的解释。在正常方式下,所打印的字符是使用当前属性直接写到显示内存中的。
在console.c 中,数据结构vc 的任何域使用宏都是可访问的,所以(例如)任何对attr
的引用,只要currcons是所指的控制台的号码,确实是引证了数据结构vc_cons[currcons]
中的域。
实际上,新内核中的vc_cons 已不再是个数据结构数组了,现在他是指针的数
组,其内容是用kmalloc()操作的。宏的使用大大地简化了代码修改的工作,因为许多代码
都无需被重写。
控制台内存到屏幕内存的实际映射和非映射是由函数set_scrmem()(他把控制台缓冲
区中的数据拷贝到显示内存中)和get_srcmem()(他把数据拷贝回控制台缓冲区中)执行
的。为了减少数据传输的次数,当前控制台的私有缓冲区是物理地映射到实际显示RAM 上的。
这意味着console.c 中的get-和set-_scrmem()是静态的,并且仅在一个控制台转换期间才
被调用。
控制台读操作
控制台读操作是由线路规程来完成的。Linux 中默认的(也是唯一的)线路规程被称为
tty_ldisc_N_TTY。线路规程也就是“通过一线路约束输入”。他是另一个函数表(我们已
习惯了这种方法,不是吗?),他是有关于设备读操作的。在termios标志的帮助下,线路
规程也即是从tty 上控制输入的规程:未处理过的数据、cbreak 和计划的方式;select();
ioctl()等等。
线路规程中的读(read)函数称为read_chan(),他读取tty 的缓冲区而不管数据是从
哪里来的。原因是通过一个tty 来到的字符是由异步硬件中断管理的。
线路规程N_TTY 也同样在tty_io.c 中,尽管以后出的内核都使用一个不同的
n_tty.c 源程式。
控制台输入的最底层是键盘管理的一部分,因此他是在keyboard.c 的
keyboard_interrupt()中处理的。
键盘管理
键盘管理简直是一场噩梦。他限于文档keyboard.c 中,里面充满了表示不同厂家键盘
的各个键码的十六进制数。
我将不对keyboard.c 进行深入讨论,因为其中没有和内核研究者有关的相关信息。
对于那些对Linux 的键盘编程确实感兴趣的人,最好的方法是从keyboard.c 的最
后一行往回看起。最底层的细节是在该文档的上半部分。
转换当前控制台

当前控制台是通过使用函数change_console()来转换的,他位于tty_io.c 中由
keyboard.c和vt.c 调用(前者响应按键的控制台转换,后者是当一个程式通过引用一个
ioctl()调用时转换控制台)。
实际的转换过程是分两步来执行的,函数complete_change_console()处理其中的第二
部分。转换的分裂意味着在一个和控制着我们正在离开的tty 的进程的可能的握手以后完成
任务。假如控制台不在进程控制之下,change_console()就会自己调用
complete_change_console()。进程需要足够的能力来成功地完成从图像到文本控制台或从
文本到图像控制台的转换,并且X服务器(例如)是其图像控制台的控制进程。
选择机制
“选择(selection)”是Linux 文本控制台的剪切(cut)和粘贴(paste)功能。这
个技巧主要是由用户级的进程来处理的,他能够用selection或gpm 的具体例子说明。用户
级的程式在控制台上使用ioctl()通知内核来加亮显示屏幕的一个区域。然后,被选择的文
本被拷贝到一个选择缓冲区。该缓冲区是console.c 中的一个静态实体。粘贴文本操作是通
过“手工地”将字符放入tty 输入队列中完成的。整个选择机制是通过#ifdef受到保护的,
所以用户在内核配置期间能够禁用他以节省几千字节的内存。
选择是个很低级的功能,因而他工作是任何其他内核活动所看不见的。这意味着许
多的#ifdef只是屏幕在以任何方式作修改之前简单地移动加亮部分。
新内核特性改善了选择的代码,鼠标指针的加亮能够和被选择的文本单独(内核
1.1.23 或更高)。而且,从1.1.73版起,被选择的文本使用了动态的缓冲区而不是静态的
了,使得内核小了4KB。
使用ioctl()操作设备
ioctl()系统调用是用户进程控制设备文档行为的入口点。Ioctl 管理是
从../../fs/ioctl.c中产生的,实际上sys_ioctl()就是在这个ioctl.c中的。标准的ioctl
请求就是在那里执行的,其他和文档相关的请求是由file_ioctl()处理的(在同一个源文
件中),而其他任何请求都分派给特定设备的ioctl()函数。
控制台设备的ioctl 资料是位于vt.c 中的,因为控制台驱动程式要将ioctl 请求分派
给vt_ioctl()。
上述信息是关于内核1.1.7x的。1.0内核是没有“驱动程式”表的,而且vt_ioctl()
是直接由file_operations()表指向的。
Ioctl的资料确实是相当让人混淆的。有些请求是和设备相关的,而有些却是和线路规
程相关的。我将试图对1.0和1.1.7x内核之间发生的任何事概要总结一下。
1.1.7x 系列内核有如下的特性:tty_ioctl.c 只实现了线路规程请求(也就是
n_tty_ioctl(),这是唯一在n_tty.c 外面的n_tty 函数),而file_operations 字段指向
tty_io.c 中的tty_ioctl()。假如请求号没有被tty_ioctl()解析出来,他就会被传到
tty->driver.ioctl 或,假如他失败时,就到tty->ldisc.ioctl。控制台的和驱动程式相
关的资料能够从vt.c 中找到,而线路规程方面的资料则在tty_ioctl.c 中。
在1.0 内核中,tty_ioctl()是在tty_ioctl.c 中的并有一般tty的file_operations
所指向。未被解析出的请求将用和1.1.7x 相似的方法被传送到特定的ioctl 函数或到线路
规程代码去。
注意,在这两种情况中,TIOCLINUX请求是在和设备无关的代码中的,这暗示着控制台
选择操作能够通过ioctl 对任何tty进行操作来配置(set_selection()总是在控制台前台
Linux 内核源代码漫游
上操作的),而这是个安全上的漏洞。这也是转移到一个更新的内核的很好理由,在新内
核中,通过仅允许终极用户来处理选择弥补了这个漏洞。
有很多请求能够被发给控制台设备,而知道他们的最好方法是浏览源程式文档vt.c。
版权任何(c) 1994 Alessandro Rubini,
rubini@pop.systemy.it




喜欢本文,那就收藏到:

    Del.icio.us Google书签 Digg Live Bookmark Technorati Furl Yahoo书签 Facebook 百度搜藏 新浪ViVi 365Key网摘 天极网摘 和讯网摘 博拉网 POCO网摘 添加到饭否 QQ书签 Digbuzz我挖网
相关评论  我也要评论
还没有关于此文章的相关评论!
  • 昵称: (为空则显示guest)
  • 评论分数: ★ ★ ★★★ ★★★★ ★★★★★
  • 评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
  • 导航
    赞助商
    文章类别
    订阅