⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 x429.html

📁 linux驱动开发
💻 HTML
📖 第 1 页 / 共 2 页
字号:
<HTML
><HEAD
><TITLE
>内核模块和用户程序的比较</TITLE
><META
NAME="GENERATOR"
CONTENT="Microsoft FrontPage 4.0"><LINK
REL="HOME"
TITLE="The Linux Kernel Module Programming Guide"
HREF="index.html"><LINK
REL="UP"
TITLE="Preliminaries"
HREF="c427.html"><LINK
REL="PREVIOUS"
TITLE="Preliminaries"
HREF="c427.html"><LINK
REL="NEXT"
TITLE="Character Device Files"
HREF="c569.html"></HEAD
><BODY
CLASS="SECT1"
BGCOLOR="#FFFFFF"
TEXT="#000000"
LINK="#0000FF"
VLINK="#840084"
ALINK="#0000FF"
><DIV
CLASS="NAVHEADER"
><TABLE
SUMMARY="Header navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TH
COLSPAN="3"
ALIGN="center"
>Linux内核驱动模块编程指南 (内核版本2.2, 2.4)</TH 
></TR
><TR
><TD
WIDTH="10%"
ALIGN="left"
VALIGN="bottom"
><A
HREF="c427.html"
ACCESSKEY="P"
>返回</A
></TD
><TD
WIDTH="80%"
ALIGN="center"
VALIGN="bottom"
>第三章. 开始热身</TD 
><TD
WIDTH="10%"
ALIGN="right"
VALIGN="bottom"
><A
HREF="c569.html"
ACCESSKEY="N"
>继续</A
></TD
></TR
></TABLE
><HR
ALIGN="LEFT"
WIDTH="100%"></DIV
><DIV
CLASS="SECT1"
><H1
CLASS="SECT1"
><A
NAME="AEN429"
></A
>3.1. 内核模块和用户程序的比较</H1 
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN431"
></A
>3.1.1. 内核模块是如何开始和结束的</H2 
><P
>用户程序通常从函数 <TT 
CLASS="FUNCTION"
>main()</TT
> 开始,执行一系列的指令并且当指令执行完成后结束程序。内核模块有一点不同。内核模块要么从函数 <TT 
CLASS="FUNCTION"
>init_module</TT
> 或是你用宏 <TT 
CLASS="FUNCTION"
>module_init</TT
> 指定的函数调用开始。这就是内核模块的入口函数。它告诉内核模块提供那些功能扩展并且让内核准备好在需要时调用它。 
当它完成这些后,该函数就执行结束了。模块在被内核调用前也什么都不做。</P
><P
>所有的模块或是调用 <TT 
CLASS="FUNCTION"
>cleanup_module</TT
> 或是你用宏 <TT 
CLASS="FUNCTION"
>module_exit</TT
> 指定的函数。这是模块的退出函数。它撤消入口函数所做的一切。例如注销入口函数所注册的功能。 </P 
><P
>所有的模块都必须有入口函数和退出函数。既然我们有不只一种方法去定义这两个函数,我将努力使用“入口函数”和“退出函数”来描述 
它们。但是当我只用 <TT 
CLASS="FUNCTION"
>init_module</TT
> 和 <TT 
CLASS="FUNCTION"
>cleanup_module</TT
>时,我希望你明白我指的是什么。</P
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN443"
></A
>3.1.2. 模块可调用的函数</H2 
><P
>程序源并不总是自己写所有用到的函数。一个常见的基本的例子就是<TT
CLASS="FUNCTION"
>printf()</TT
>。你使用这些C标准库,libc提供的库函数。这些函数(像<TT
CLASS="FUNCTION"
>printf()</TT
>) 
实际上在连接之前并不进入你的程序。在连接时这些函数调用才会指向 
你调用的库,从而使你的代码最终可以执行。</P
><P
>内核模块有所不同。在hello world模块中你也许已经注意到了我们使用的函数<TT 
CLASS="FUNCTION"
>printk()</TT
> 却没有包含标准I/O库。这是因为模块是在insmod加载时才连接的目标文件。那些要用到的函数的符号链接是内核自己提供的。 
也就是说,你可以在内核模块中使用的函数只能来自内核本身。如果你对内核提供了哪些函数符号链接感兴趣,看一看文件 <TT 
CLASS="FILENAME"
>/proc/ksyms</TT
>。</P
><P
>需要注意的一点是库函数和系统调用的区别。库函数是高层的,万全运行在用户空间,为程序员提供调用真正的在幕后 
完成实际事务的系统调用的更方便的接口。系统调用在内核态运行并且由内核自己提供。标准C库函数 <TT 
CLASS="FUNCTION"
>printf()</TT
> 可以被看做是一个通用的输出语句,但它实际做的是将数据转化为符合格式的字符串并且调用系统调用 
<TT
CLASS="FUNCTION"
>write()</TT
>输出这些字符串。</P
><P
> 是否想看一看 <TT 
CLASS="FUNCTION"
>printf()</TT
> 究竟使用了哪些系统调用? 这很容易,编译下面的代码。 </P 
><TABLE
BORDER="1"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="SCREEN"
>    #include &lt;stdio.h&gt;
    int main(void)
    { printf(&quot;hello&quot;); return 0; }
				</PRE
></FONT
></TD
></TR
></TABLE
><P
>使用命令 <B 
CLASS="COMMAND"
>gcc -Wall -o hello hello.c</B
>  编译。用命令 <B 
CLASS="COMMAND"
>strace hello</B
> 运行该可执行文件。是否很惊讶? 
每一行都和一个系统调用相对应。strace<A
NAME="AEN469"
HREF="#FTN.AEN469"
><SPAN
CLASS="footnote"
>[1]</SPAN
></A

> 是一个非常有用的程序,它可以告诉你程序使用了哪些系统调用和这些系统调用的参数,返回值。 
这是一个极有价值的查看程序在干什么的工具。在输出的末尾,你应该看到这样类似的一行“ 
<TT
CLASS="FUNCTION"
>write(1, "hello", 5hello)</TT
>”。这就是我们要找的。藏在面具 <TT 
CLASS="FUNCTION"
>printf()</TT
> 的真实面目。既然绝大多数人使用库函数来对文件I/O进行操作(像 fopen, fputs, fclose)。 
你可以查看man说明的第二部分使用命令 <B 
CLASS="COMMAND"
>man 2 write</B
>。man说明的第二部分专门介绍系统调用(像 <TT 
CLASS="FUNCTION"
>kill()</TT
> 和 <TT 
CLASS="FUNCTION"
>read()</TT
>)。 man说明的第三部分则专门介绍你可能更熟悉的库函数,(像 <TT 
CLASS="FUNCTION"
>cosh()</TT
> 和 <TT 
CLASS="FUNCTION"
>random()</TT
>)。</P
><P
>你甚至可以编写代码去覆盖系统调用,正如我们不久要做的。骇客常这样做来为系统安装后门或木马。 
但你可以用它来完成一些更有益的事,像让内核在每次某人删除文件时输出“ <EM 
>Tee hee, that tickles! ”</EM
> 的信息。</P 
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN480"

></A
>3.1.3. 用户空间和内核空间</H2 
><P
>内核全权负责对硬件资源的访问,不管被访问的是显示卡,硬盘,还是内存。用户程序常为这些资源竞争。就如同我在保存这 
份文档同时本地数据库正在更新。我的编辑器vim进程和数据库更新进程同时要求访问硬盘。内核必须使这些请求有条不紊的进行, 
而不是随用户的意愿提供计算机资源。   
为方便实现这种机制, <SPAN 
CLASS="ACRONYM"
>CPU</SPAN
> 可以在不同的状态运行。不同的状态赋予不同的你对系统操作的自由。Intel 80836 架构有四种状态。Unix只使用了其中 
的两种,最高级的状态(操作状态0,即“超级状态”,可以执行任何操作)和最低级的状态(即“用户状态”)。
</P
><P
>回忆以下我们对库函数和系统调用的讨论,一般库函数在用户态执行。库函数调用一个或几个系统调用,
而这些系统调用为库函数完成工作,但是在超级状态。一旦系统调用完成工作后系统调用就返回同时程序也返回用户态。
</P
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN485"
></A
>3.1.4. 命名空间</H2 
><P
>如果你只是写一些短小的C程序,你可为你的变量起一个方便的和易于理解的变量名。但是,如果你写的代码只是 
许多其它人写的代码的一部分,你的全局一些就会与其中的全局变量发生冲突。另一个情况是一个程序中有太多的 
难以理解的变量名,这又会导致命 <EM 
>名空间的污染</EM
>。在大型项目中,必须努力记住保留的变量名,或为独一无二的命名使用一种统一的方法。
</P
><P
>当编写内核代码时,即使是最小的模块也会同整个内核连接,所以这的确是个令人头痛的问题。最好的解决方法是声明你的变量为 
 <SPAN
CLASS="TYPE"
>static</SPAN
> 静态的并且为你的符号使用一个定义的很好的前缀。传统中,使用小写字母的内核前缀。如果你不想将所有的东西都声明为 <SPAN 
CLASS="TYPE"
>static</SPAN
>静态的,另一个选择是声明一个<TT
CLASS="VARNAME"
>symbol table</TT
> (符号表)并向内核注册。我们将在以后讨论。</P 
><P
>文件 <TT 
CLASS="FILENAME"
>/proc/ksyms</TT
> 保存着内核知道的所有的符号,你可以访问它们,因为它们是内核代码空间的一部分。</P 
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN502"
></A
>3.1.5. 代码空间</H2 
><P
>内存管理是一个非常复杂的课题。O'Reilly的《Understanding The Linux Kernel》绝大部分都在讨论内存管理!我们 
并不准备专注于内存管理,但有一些东西还是得知道的。
</P
><P
>如果你没有认真考虑过内存设计缺陷意味着什么,你也许会惊讶的获知一个指针并不指向一个确切的内存区域。当一个进程建立时, 
内核为它分配一部分确切的实际内存空间并把它交给进程,被进程的代码,变量,堆栈和其它一些计算机学的专家才明白的东西使用 
<A
NAME="AEN516"
HREF="#FTN.AEN516"
><SPAN
CLASS="footnote"
>[2]</SPAN
></A
>。这些内存从 $0$ 开始并可以扩展到需要的地方。 
这些内存空间并不重叠,所以即使进程访问同一个内存地址,例如 <TT 
CLASS="LITERAL"
>0xbffff978</TT
>,真实的物理内存地址其实是不同的。 进程实际指向的是一块被分配的内存中以 <TT 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -