Linux设备驱动调试技术 1

来源:互联网 发布:夜刀神十香 知乎 编辑:程序博客网 时间:2024/06/01 20:33

对于任何一位内核代码的编写者来说,最急迫的问题之一就是如何完成调试。由于内核是一个不与特定进程相关的功能集合,所以内核代码无法轻易地放在调试器中执行,而且也很难跟踪。同样,要想复现内核代码中的错误也是相当困难的,因为这种错误可能导致整个系统崩溃,这样也就破坏了可以用来跟踪它们的现场。 
本章将介绍在这种令人痛苦的环境下监视内核代码并跟踪错误的技术。

4.1  通过打印调试 
最普通的调试技术就是监视,即在应用程序编程中,在一些适当的地点调用printf 显示监视信息。调试内核代码的时候,则可以用printk 来完成相同的工作。

4.1.1  printk 
在前面的章节中,我们只是简单假设 printk 工作起来和 printf 很类似。现在则是介绍它们之间一些不同点的时候了。

其中一个差别就是,通过附加不同日志级别(loglevel),或者说消息优先级,可让printk根据这些级别所标示的严重程度,对消息进行分类。一般采用宏来指示日志级别,例如,KERN_INFO,我们在前面已经看到它被添加在一些打印语句的前面,它就是一个可以使用的消息日志级别。日志级别宏展开为一个字符串,在编译时由预处理器将它和消息文本拼接在一起;这也就是为什么下面的例子中优先级和格式字串间没有逗号的原因。下面有两个printk 的例子,一个是调试信息,一个是临界信息:


printk(KERN_DEBUG "Here I am: %s:%i\n", _ _FILE_ _, _ _LINE__); 
printk(KERN_CRIT "I'm trashed; giving up on %p\n", ptr);

 


在头文件 <linux/kernel.h> 中定义了 8种可用的日志级别字符串。

KERN_EMERG 
用于紧急事件消息,它们一般是系统崩溃之前提示的消息。

KERN_ALERT 
用于需要立即采取动作的情况。

KERN_CRIT 
临界状态,通常涉及严重的硬件或软件操作失败。

KERN_ERR 
用于报告错误状态;设备驱动程序会经常使用 KERN_ERR 来报告来自硬件的问题。

KERN_WARNING 
对可能出现问题的情况进行警告,这类情况通常不会对系统造成严重问题。

KERN_NOTICE 
有必要进行提示的正常情形。许多与安全相关的状况用这个级别进行汇报。

KERN_INFO 
提示性信息。很多驱动程序在启动的时候,以这个级别打印出它们找到的硬件信息。

KERN_DEBUG 
用于调试信息。

每个字符串(以宏的形式展开)代表一个尖括号中的整数。整数值的范围从0到7,数值越小,优先级就越高。

没有指定优先级的 printk 语句默认采用的级别是 DEFAULT_MESSAGE_LOGLEVEL,这个宏在文件kernel/printk.c 中指定为一个整数值。在 Linux的开发过程中,这个默认的级别值已经有过好几次变化,所以我们建议读者始终指定一个明确的级别。

根据日志级别,内核可能会把消息打印到当前控制台上,这个控制台可以是一个字符模式的终端、一个串口打印机或是一个并口打印机。如果优先级小于console_loglevel 这个整数值的话,消息才能显示出来。如果系统同时运行了 klogd 和 syslogd,则无论 console_loglevel 为何值,内核消息都将追加到 /var/log/messages中(否则的话,除此之外的处理方式就依赖于对 syslogd 的设置)。如果 klogd没有运行,这些消息就不会传递到用户空间,这种情况下,就只好查看 /proc/kmsg 了。

变量 console_loglevel 的初始值是DEFAULT_CONSOLE_LOGLEVEL,而且还可以通过sys_syslog 系统调用进行修改。调用 klogd 时可以指定-c 开关选项来修改这个变量, klogd 的 man 手册页对此有详细说明。注意,要修改它的当前值,必须先杀掉 klogd,再加-c选项重新启动它。此外,还可以编写程序来改变控制台日志级别。读者可以在 O’Reilly 的 FTP 站点提供的源文件miscprogs/setlevel.c 里找到这样的一段程序。新优先级被指定为一个 1 到 8 之间的整数值。如果值被设为1,则只有级别为 0(KERN_EMERG) 的消息才能到达控制台;如果设为 8,则包括调试信息在内的所有消息都能显示出来。

如果在控制台上工作,而且常常遇到内核错误(参见本章后面的“调试系统故障”一节)的话,就有必要降低日志级别,因为出错处理代码会把console_loglevel增为它的最大数值,导致随后的所有消息都显示在控制台上。如果需要查看调试信息,就有必要提高日志级别;这在远程调试内核,并且在交互会话未使用文本控制台的情况下,是很有帮助的。

从2.1.31这个版本起,可以通过文本文件 /proc/sys/kernel/printk来读取和修改控制台的日志级别。这个文件容纳了 4个整数值。读者可能会对前面两个感兴趣:控制台的当前日志级别和默认日志级别。例如,在最近的这些内核版本中,可以通过简单地输入下面的命令使所有的内核消息得到显示:


# echo 8 > /proc/sys/kernel/printk

 


不过,如果仍在 2.0 版本下的话,就需要使用 setlevel 这样的工具了。

现在大家应该清楚为什么在 hello.c范例中使用 <1>这些标记了,它们用来确保这些消息能在控制台上显示出来。

对于控制台日志策略,Linux考虑到了某些灵活性,也就是说,可以发送消息到一个指定的虚拟控制台(假如控制台是文本屏幕的话)。默认情况下,“控制台”就是当前地虚拟终端。可以在任何一个控制台设备上调用ioctl(TIOCLINUX),来指定接收消息的虚拟终端。下面的 setconsole 程序,可选择专门用来接收内核消息的控制台;这个程序必须由超级用户运行,在 misc-progs目录里可以找到它。下面是程序的代码:


int main(int argc, char **argv) 

  char bytes[2] = {11,0};

  if (argc==2) bytes[1] = atoi(argv[1]); 
  else { 
     fprintf(stderr, "%s: need a single arg\n",argv[0]);exit(1); 
 
  if (ioctl(STDIN_FILENO, TIOCLINUX,bytes)<0){    
     fprintf(stderr,"%s: ioctl(stdin, TIOCLINUX):%s\n", 
             argv[0], strerror(errno)); 
     exit(1); 
 
  exit(0); 
}

 


setconsole 使用了特殊的ioctl命令:TIOCLINUX ,这个命令可以完成一些特定的 Linux 功能。使用TIOCLINUX时,需要传给它一个指向字节数组的指针参数。数组的第一个字节指定所请求子命令的数字,接下去的字节所具有的功能则由这个子命令决定。在setconsole 中,使用的子命令是 11,后面那个字节(存于bytes[1]中)标识虚拟控制台。关于 TIOCLINUX的详尽描述可以在内核源码中的 drivers/char/tty_io.c 文件得到。

4.1.2  消息如何被记录 
printk 函数将消息写到一个长度为 LOG_BUF_LEN(定义在 kernel/printk.c中)字节的循环缓冲区中,然后唤醒任何正在等待消息的进程,即那些睡眠在 syslog 系统调用上的进程,或者读取 /proc/kmesg的进程。这两个访问日志引擎的接口几乎是等价的,不过请注意,对 /proc/kmesg进行读操作时,日志缓冲区中被读取的数据就不再保留,而 syslog系统调用却能随意地返回日志数据,并保留这些数据以便其它进程也能使用。一般而言,读 /proc 文件要容易些,这使它成为 klogd的默认方法。

手工读取内核消息时,在停止klogd之后,可以发现 /proc文件很象一个FIFO,读进程会阻塞在里面以等待更多的数据。显然,如果已经有 klogd或其它的进程正在读取相同的数据,就不能采用这种方法进行消息读取,因为会与这些进程发生竞争。

如果循环缓冲区填满了,printk就绕回缓冲区的开始处填写新数据,覆盖最陈旧的数据,于是记录进程就会丢失最早的数据。但与使用循环缓冲区所带来的好处相比,这个问题可以忽略不计。例如,循环缓冲区可以使系统在没有记录进程的情况下照样运行,同时覆盖那些不再会有人去读的旧数据,从而使内存的浪费减到最少。Linux消息处理方法的另一个特点是,可以在任何地方调用printk,甚至在中断处理函数里也可以调用,而且对数据量的大小没有限制。而这个方法的唯一缺点就是可能丢失某些数据。

klogd 运行时,会读取内核消息并将它们分发到 syslogd,syslogd 随后查看 /etc/syslog.conf,找出处理这些数据的方法。syslogd 根据设施和优先级对消息进行区分;这两者的允许值均定义在<sys/syslog.h> 中。内核消息由 LOG_KERN设施记录,并以 printk 中使用的优先级记录(例如,printk 中使用的 KERN_ERR对应于syslogd 中的LOG_ERR)。如果没有运行 klogd,数据将保留在循环缓冲区中,直到某个进程读取或缓冲区溢出为止。

如果想避免因为来自驱动程序的大量监视信息而扰乱系统日志,则可以为 klogd 指定 -f (file) 选项,指示 klogd将消息保存到某个特定的文件,或者修改 /etc/syslog.conf来适应自己的需求。另一种可能的办法是采取强硬措施:杀掉klogd,而将消息详细地打印到空闲的虚拟终端上。*


注: 例如,使用下面的命令可设置 10 号终端用于消息的显示: 
setlevel 8 
setconsole 10

 


或者在一个未使用的 xterm 上执行cat /proc/kmesg来显示消息。

4.1.3  开启及关闭消息 
在驱动程序开发的初期阶段,printk对于调试和测试新代码是相当有帮助的。不过,当正式发布驱动程序时,就得删除这些打印语句,或至少让它们失效。不幸的是,你可能会发现这样的情况,在删除了那些已被认为不再需要的提示消息后,又需要实现一个新的功能(或是有人发现了一个bug),这时,又希望至少把一部分消息重新开启。这两个问题可以通过几个办法解决,以便全局地开启或禁止消息,并能对个别消息进行开关控制。

我们在这里给出了一个编写 printk调用的方法,可个别或全局地对它们进行开关;这个技巧是定义一个宏,在需要时,这个宏展开为一个printk(或printf)调用。

可以通过在宏名字中删减或增加一个字母,打开或关闭每一条打印语句。

编译前修改 CFLAGS 变量,则可以一次关闭所有消息。

同样的打印语句既可以用在内核态也可以用在用户态,因此,关于这些额外的信息,驱动和测试程序可以用同样的方法来进行管理。

下面这些来自 scull.h 的代码,就实现了这些功能。


#undefPDEBUG             
#ifdef SCULL_DEBUG 
ifdef _ _KERNEL_ _ 
    
   definePDEBUG(fmt, args...) printk( KERN_DEBUG "scull: "fmt, 
                                      ## args) 
else 
    
   definePDEBUG(fmt, args...) fprintf(stderr, fmt, ##args) 
endif 
#else 
define PDEBUG(fmt, args...) 
#endif

#undef PDEBUGG 
#define PDEBUGG(fmt, args...)

 


符号 PDEBUG依赖于是否定义了SCULL_DEBUG,它能根据代码所运行的环境选择合适的方式显示信息:内核态运行时使用printk系统调用;用户态下则使用libc调用fprintf,向标准错误设备进行输出。符号PDEBUGG则什么也不做;它可以用来将打印语句注释掉,而不必把它们完全删除。

为了进一步简化这个过程,可以在 Makefile加上下面几行:


# Comment/uncomment the following line to disable/enabledebugging 
DEBUG = y

# Add your debugging flag (or not) toCFLAGS 
ifeq ($(DEBUG),y) 
DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expandinlines 
else 
DEBFLAGS = -O2 
endif

CFLAGS += $(DEBFLAGS)

 


本节所给出的宏依赖于gcc 对ANSI C预编译器的扩展,这种扩展支持了带可变数目参数的宏。对 gcc的这种依赖并不是什么问题,因为内核对 gcc 特性的依赖更强。此外,Makefile依赖于 GNU 的make版本;基于同样的道理,这也不是什么问题。

如果读者熟悉 C预编译器,可以将上面的定义进行扩展,实现“调试级别”的概念,这需要定义一组不同的级别,并为每个级别赋一个整数(或位掩码),用以决定各个级别消息的详细程度。

但是每一个驱动程序都会有自身的功能和监视需求。良好的编程技术在于选择灵活性和效率的最佳折衷点,对读者来说,我们无法预知最合适的点在哪里。记住,预处理程序的条件语句(以及代码中的常量表达式)只在编译时执行,要再次打开或关闭消息必须重新编译。另一种方法就是使用C条件语句,它在运行时执行,因此可以在程序运行期间打开或关闭消息。这是个很好的功能,但每次代码执行时系统都要进行额外的处理,甚至在消息关闭后仍然会影响性能。有时这种性能损失是无法接受的。

在很多情况下,本节提到的这些宏都已被证实是很有用的,仅有的缺点是每次开启和关闭消息显示时都要重新编译模块。

4.2  通过查询调试 
上一节讲述了 printk 是如何工作的以及如何使用它,但还没谈到它的缺点。

由于 syslogd 会一直保持对其输出文件的同步刷新,每打印一行都会引起一次磁盘操作,因此大量使用 printk会严重降低系统性能。从 syslogd的角度来看,这样的处理是正确的。它试图把每件事情都记录到磁盘上,以防系统万一崩溃时,最后的记录信息能反应崩溃前的状况;然而,因处理调试信息而使系统性能减慢,是大家所不希望的。这个问题可以通过在/etc/syslogd.conf 中日志文件的名字前面,前缀一个减号符解决。*


注: 这个减号是个“特殊”标记,避免 syslogd 在每次出现新信息时都去刷新磁盘文件,这些内容记述在 syslog.conf(5)中,这个手册页很值得一读。

 

 

修改配置文件带来的问题在于,在完成调试之后改动将依旧保留;即使在一般的系统操作中,当希望尽快把信息刷新到磁盘时,也是如此。如果不愿作这种持久性修改的话,另一个选择是运行一个非klogd 程序(如前面介绍的cat /proc/kmesg),但这样并不能为通常的系统操作提供一个合适的环境。

多数情况中,获取相关信息的最好方法是在需要的时候才去查询系统信息,而不是持续不断地产生数据。实际上,每个 Unix系统都提供了很多工具,用于获取系统信息,如:ps、netstat、vmstat等等。

驱动程序开发人员对系统进行查询时,可以采用两种主要的技术:在 /proc 文件系统中创建文件,或者使用驱动程序的 ioctl方法。/proc 方式的另一个选择是使用 devfs,不过用于信息查找时,/proc 更为简单一些。

4.2.1  使用 /proc 文件系统 
/proc 文件系统是一种特殊的、由程序创建的文件系统,内核使用它向外界输出信息。/proc下面的每个文件都绑定于一个内核函数,这个函数在文件被读取时,动态地生成文件的“内容”。我们已经见到过这类文件的一些输出情况,例如,/proc/modules列出的是当前载入模块的列表。

Linux系统对/proc的使用很频繁。现代Linux系统中的很多工具都是通过 /proc 来获取它们的信息,例如 ps、top 和uptime。有些设备驱动程序也通过 /proc 输出信息,你的驱动程序当然也可以这么做。因为 /proc文件系统是动态的,所以驱动程序模块可以在任何时候添加或删除其中的文件项。

特征完全的 /proc 文件项相当复杂;在所有的这些特征当中,有一点要指出的是,这些 /proc文件不仅可以用于读出数据,也可以用于写入数据。不过,大多数时候,/proc文件项是只读文件。本节将只涉及简单的只读情形。如果有兴趣实现更为复杂的事情,读者可以先在这里了解基础知识,然后参考内核源码来建立完整的认识。

所有使用 /proc 的模块必须包含<linux/proc_fs.h>,通过这个头文件定义正确的函数。

为创建一个只读 /proc 文件,驱动程序必须实现一个函数,用于在文件读取时生成数据。当某个进程读这个文件时(使用 read系统调用),请求会通过两个不同接口的其中之一发送到驱动程序模块,使用哪个接口取决于注册情况。我们先把注册放到本节后面,先直接讲述读接口。

无论采用哪个接口,在这两种情况下,内核都会分配一页内存(也就是 PAGE_SIZE个字节),驱动程序向这片内存写入将返回给用户空间的数据。

推荐的接口是 read_proc,不过还有一个名为 get_info 的老一点的接口。


int (*read_proc)(char *page, char **start, off_t offset, int count,int *eof, void *data);

 


参数表中的 page 指针指向将写入数据的缓冲区;start被函数用来说明有意义的数据写在页面的什么位置(对此后面还将进一步谈到);offset 和 count 这两个参数与在 read实现中的用法相同。eof 参数指向一个整型数,当没有数据可返回时,驱动程序必须设置这个参数;data参数是一个驱动程序特有的数据指针,可用于内部记录。*


注: 纵览全书,我们还会发现这样的一些指针;它们表示了这类处理中有关的“对象”,与C++ 中的同类处理有些相似。

 


这个函数可以在2.4内核中使用,如果使用我们的 sysdep.h 头文件,那么在2.2内核中也可以用这个函数。


int (*get_info)(char *page, char **start, off_t offset, intcount);  

 


get_info 是一个用来读取 /proc 文件的较老接口。所有的参数与 read_proc中的对应参数用法相同。缺少的是报告到达文件尾的指针和由data指针带来的面向对象风格。这个函数可以用在所有我们感兴趣的内核版本中(尽管在它 2.0 版本的实现中有一个额外未用的参数)。

这两个函数的返回值都是实际放入页面缓冲区的数据的字节数,这一点与 read 函数对其它类型文件的处理相同。另外还有 *eof 和*start 这两个输出值。eof 只是一个简单的标记,而 start 的用法就有点复杂了。

对于 /proc 文件系统的用户扩展,其最初实现中的主要问题在于,数据传输只使用单个内存页面。这样就把用户文件的总体尺寸限制在了4KB 以内(或者是适合于主机平台的其它值)。start 参数在这里就是用来实现大数据文件的,不过该参数可以被忽略。

如果 proc_read 函数不对 *start 指针进行设置(它最初为 NULL),内核就会假定 offset参数被忽略,并且数据页包含了返回给用户空间的整个文件。反之,如果需要通过多个片段创建一个更大的文件,则可以把 *start赋值为页面指针,因此调用者也就知道了新数据放在缓冲区的开始位置。当然,应该跳过前 offset个字节的数据,因为这些数据已经在前面的调用中返回。

长久以来,关于 /proc 文件还有另一个主要问题,这也是 start 意图解决的一个问题。有时,在连续的 read调用之间,内核数据结构的 ASCII 表述会发生变化,以至于读进程发现前后两次调用所获得的数据不一致。如果把 *start设为一个小的整数值,调用程序可以利用它来增加 filp->f_pos的值,而不依赖于返回的数据量,因此也就使 f_pos 成为read_proc 或 get_info 程序中的一个内部记录值。例如,如果read_proc 函数从一个大的结构数组返回数据,并且这些结构的前 5 个已经在第一次调用中返回,那么可将 *start 设置为5。下次调用中这个值将被作为偏移量;驱动程序也就知道应该从数组的第六个结构开始返回数据。这种方法被它的作者称作“hack”,可以在/fs/proc/generic.c 中看到。

现在我们来看个例子。下面是scull 设备 read_proc 函数的简单实现:


int scull_read_procmem(char *buf, char **start, off_toffset, 
                int count, int *eof, void *data) 

  int i, j, len = 0; 
  int limit = count - 80;

  for (i = 0; i < scull_nr_devs&& len <= limit;i++) { 
     Scull_Dev *d = &scull_devices[i]; 
     if(down_interruptible(&d->sem)) 
             return -ERESTARTSYS; 
     len += sprintf(buf+len,"\nDevice %i: qset %i, q %i, sz%li\n", 
                    i, d->qset, d->quantum,d->size); 
     for (; d && len <=limit; d = d->next) {  
         len += sprintf(buf+len, "  item at %p, qset at%p\n", d, 
                                 d->data); 
         if (d->data &&!d->next)  
             for (j = 0; j < d->qset; j++){ 
                 if (d->data[j]) 
                     len +=sprintf(buf+len,"   % 4i: %8p\n", 
                                                 j,d->data[j]); 
             
     
     up(&scull_devices[i].sem); 
 
  *eof = 1; 
  return len; 
}

 


这是一个相当典型的 read_proc 实现。它假定决不会有这样的需求,即生成多于一页的数据,因此忽略了 start 和 offset值。但是,小心不要超出缓冲区,以防万一。

使用 get_info 接口的 /proc 函数与上面说明的 read_proc非常相似,除了没有最后的那两个参数。既然这样,则通过返回少于调用者预期的数据(也就是少于 count参数),来提示已到达文件尾。

一旦定义好了一个 read_proc 函数,就需要把它与一个 /proc文件项连接起来。依赖于将要支持的内核版本,有两种方法可以建立这样的连接。最容易的方法是简单地调用create_proc_read_entry,但这只能用于2.4内核(如果使用我们的 sysdep.h 头文件,则也可用于 2.2内核)。下面就是 scull 使用的调用,以 /proc/scullmem 的形式来提供 /proc 功能。


create_proc_read_entry("scullmem", 
                      
                    NULL , 
                    scull_read_procmem, 
                    NULL );

 


这个函数的参数表包括:/proc文件项的名称、应用于该文件项的文件许可权限(0是个特殊值,会被转换为一个默认的、完全可读模式的掩码)、文件父目录的proc_dir_entry 指针(我们使用 NULL 值使该文件项直接定位在 /proc 下)、指向 read_proc的函数指针,以及将传递给 read_proc 函数的数据指针。

目录项指针(proc_dir_entry)可用来在 /proc 下创建完整的目录层次结构。不过请注意,将文件项置于 /proc的子目录中有更为简单的方法,即把目录名称作为文件项名称的一部分――只要目录本身已经存在。例如,有个新的约定,要求设备驱动程序对应的/proc 文件项应转移到子目录 driver/ 中;scull 可以简单地指定它的文件项名称为driver/scullmem,从而把它的 /proc 文件放到这个子目录中。

当然,在模块卸载时,/proc 中的文件项也应被删除。 remove_proc_entry 就是用来撤消create_proc_read_entry 所做工作的函数。


remove_proc_entry("scullmem", NULL );

 


另一个创建 /proc 文件项的方法是,创建并初始化一个 proc_dir_entry 结构,并将该结构传递给函数proc_register_dynamic (2.0 版本)或 proc_register(2.2版本,如果结构中的索引节点号为0,该函数即认为是动态文件)。作为一个例子,当在2.0内核的头文件下进行编译时,考虑下面 scull所使用的这些代码:


static int scull_get_info(char *buf, char **start, off_toffset, 
             int len, int unused) 

  int eof = 0; 
  return scull_read_procmem (buf, start, offset,len, &eof, NULL); 
}

struct proc_dir_entry scull_proc_entry ={ 
     namelen:   8, 
     name:      "scullmem", 
     mode:      S_IFREG | S_IRUGO, 
     nlink:     1, 
     get_info:  scull_get_info, 
};

static void scull_create_proc() 

 proc_register_dynamic(&proc_root,&scull_proc_entry); 
}

static void scull_remove_proc() 

  proc_unregister(&proc_root,scull_proc_entry.low_ino); 
}

 


代码声明了一个使用 get_info 接口的函数,并填写了一个 proc_dir_entry 结构,用于对文件系统进行注册。

这段代码借助sysdep.h 中宏定义的支持,提供了 2.0 和 2.4 内核之间的兼容性。因为 2.0 内核不支持read_proc,它使用了 get_info 接口。如果对 #ifdef 作一些更多的处理,可以使这段代码在 2.2 内核中使用read_proc,不过这样收益并不大。

4.2.2  ioctl 方法 
ioctl是作用于文件描述符之上的一个系统调用,我们会在下一章介绍它的用法;它接收一个“命令”号,用以标识将执行的命令;以及另一个(可选的)参数,通常是个指针。

做为替代/proc文件系统的方法,可以为调试设计若干ioctl命令。这些命令从驱动程序复制相关数据到用户空间,在用户空间中可以查看这些数据。

使用ioctl 获取信息比起 /proc 来要困难一些,因为需要另一个程序调用 ioctl并显示结果。这个程序是必须编写并编译的,而且要和测试模块配合一致。从另一方面来说,相对实现 /proc文件所需的工作,驱动程序的编码则更为容易些。

有时 ioctl 是获取信息的最好方法,因为它比起读 /proc要快得多。如果在数据写到屏幕之前要完成某些处理工作,以二进制获取数据要比读取文本文件有效得多。此外,ioctl并不要求把数据分割成不超过一个内存页面的片断。

ioctl方法的一个优点是,在结束调试之后,用来取得信息的这些命令仍可以保留在驱动程序中。/proc文件对任何查看这个目录的人都是可见的(很多人可能会纳闷“这些奇怪的文件是用来做什么的”),然而与/proc文件不同,未公开的 ioctl命令通常都不会被注意到。此外,万一驱动程序有什么异常,这些命令仍然可以用来调试。唯一的缺点就是模块会稍微大一些。

0 0
原创粉丝点击