linux编程写一个用户程序及字符设备驱动?

驱动程序可以使用ioctl执行硬件控制。

,,,:可选参数:插入*argp,具体内容依赖于cmd

inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。

cmd 由用户空间直接不经修改的传递给驱动程序

size:表示所涉及的用户数据大小,通常为13位或是14位,具体可通过宏_IOC_SIZEBITS找到针对特定体系结构的具体数值。内核不会检查这个位字段,对该字段的检查可以帮助我们检测用户空间的错误。

在使用ioctl命令编号时,一定要避免与预定义命令重复,否则,命令冲突,设备不会响应

下列ioctl命令对任何文件(包括设备特定文件)都是预定义的:

如何使用ioctl的附加参数:arg

1:arg是个整数,那简单,直接用

2:arg是个指针,麻烦点,需检测后才能用

分析:使用指针,首先得保证指针指向的地址合法。因此,在使用这个指针之前,我们应该使用

addr: 一个用户空间的地址

如果在该地址处既要读取,又要写入,则应该用:VERIFY_WRITE,因为它是VERIFY_READ的超集

注意:首先, access_ok不做校验内存存取的完整工作; 它只检查内存引用是否在这个进程有合理权限的内存范围中,且确保这个地址不指向内核空间内存。其次,大部分驱动代码不需要真正调用 access_ok,而直接使用put_user(datum, ptr)和get_user(local, ptr),它们带有校验的功能,确保进程能够写入给定的内存地址,成功时返回 0, 并且在错误时返回 -EFAULT.。

/*抽取类型和编号位字段,并拒绝错误的命令号:在调用access_ok之前返回ENOTTY(不恰当的ioctl)*/

/*方向是一个位掩码,而VERIFY_WRITE用于R/W*传输。“类型”是针对用户空间而言,而access_ok面向内核,因此,读取和写入,恰好相反*/

使用时,速度快,不做类型检查,使用时可以给ptr传递任意类型的指针参数,只要是个用户空间的地址就行,传递的数据大小依赖于ptr参数的类型。

一般用法:实现一个读取方法时,可以调用__put_user来节省几个时钟周期,或者在复制多项数据之前调用一次access_ok,像上面代码一样。

接收的数据被保存在局部变量local中,返回值说明其是否正确。同样,__get_user应该在操作地址被access_ok后使用。

来由:驱动程序必须进行附加的检查以确认用户是否有权进行请求的操作

权能作用:基于权能的系统抛弃了那种要么全有,要么全无的特权分配方式,而是把特权操作划分成了独立的组。

在执行一项特权之前,应先检查其是否具有这个权利

CAP_NET_ADMIN /*进行网络管理任务的能力, 包括那些能够影响网络接口的任务*/

该文重点给出了三个实例来讲解Linux驱动,使新手快速、从代码层了解什么是Linux设备驱动。 
本文算是笔记,大体上是翻译该文的前两部分,即前两个实例,这两个例子都可能正确成功运行。

- 微处理器编程.对处理器的工作原理有一定的了解,如内存管理、中断等

写设备驱动时,了解“用户空间”和“内核空间”之间的区别是非常重要的。

 - 内核空间。Linux内核简单并高效地管理着机器的硬件,为用户提供简单并
 规范的编程接口。同样地,内核,特别是内核中的驱动,是用户/程序员与硬
 件之间的桥梁或接口。内核的任何例程或函数(比如模块、驱动)都属于内核
 - 用户空间。用户程序,比如unix shell或其他的gui应用程序(比如
 kpresenter),都属于用户空间。显然,这些应用程序都要与硬件打交道。但是
 它们不并直接操作硬件,而是通过内核提供的函数来实现。

内核为用户空间提供了一系列的例程或函数,用户的应用程序利用这些接口来与硬件交互 
。通常,在UNIX或Linux系统中,这种对话是通过函数或子程序来读写文件的。原因是从 
用户的角度来看,UNIX设备就是文件。

另一方面,在内核空间中Linux也提供了一系列的函数或子程序来完成底层与硬件的交互 
,并允许从内核向用户空间传递信息。

通常,每个用户空间的(设备或文件允许使用的)函数,都能在内核空间中找到一个类似 
的函数,(允许信息从内核传递给用户空间,反之亦然)

内核空间中有许多函数用于控制硬件或在内核与硬件之间交互信息。

现在将展示如何完成第一个驱动,在内核中将看作模块

2.6.x版本后的内核,编译模块会略微复杂一点。首先,需要有一份完整的、编译过的内 
核源码树。在下面的文字中,将假设使用2。6。8版本的内核。

与之前版本的内核不同,现在编译模块时使用的内核需要与模块将要加载的内核相同。 
编译上面的文件,可以使用命令:

这个极其简单的模块就属于内核空间,一旦其被加载,它就是内核空间的一部分。 
在用户空间,可以使用下面的命令加载它,需要root权限:

insmod 这个命令用于为内核加载模块。尽管现在我们已经加载了nothing.ko这个模块, 
但是这个模块毕竟没有任何用处。

可以通过查看系统里已加载的模块来检查是否已经成功加载了nothing.ko

最后,需要卸载该模块时使用下面的命令:

重新使用lsmod,可以发现nothing模块已经不在了。

为最高优先级(数字越低,优先级越高)。这样,不仅可以在内核系统日志中看到该 
打印信息,还能在系统控制台接收到该打印信息。

可以用之前的命令来编译这个模块,此时只需要将模块名加入到Makefile文件中即可:

本文的其他部分,将Makefile作为给读者的练习。一个完整的Makefile文件可以编译 
本教程中的所有示例模块。

当模块被加载或卸载时,通过printk打印的信息将会出现在系统控制台。如果打印 
信息没有出现在终端里,则可通过dmesg命令或查看系统日志文件(cat 

接着将介绍如何构建一个完整的设备驱动:memory.c。可以从该设备中读取一个字符, 
也可向其写入一个字符。这个设备并没有实际意义,只是因为它是一个完整的 
驱动程序,遂将其作为一个实例来说明。它很容易实现,因为它并不是一个正真的 
硬件设备的接口(除了电脑本身)。

这个驱动中,要添加几个在设备驱动程序中频繁出现的#inclu#include

几个通常用来操作文件的函数。这些在后面会详细介绍到。接着,向内核声明初始化和 
退出函数-加载和卸载模块时使用的。最后,声明驱动的全局变量:memory_major表示驱 
动的主驱动号,memory_buffer指向一块用于存储驱动数据的内存区域。

在UNIX和Linux中,从用户空间访问设备与访问文件相同。这些设备文件通常位于/dev目 

要做到这一点,一个文件(将用于访问设备驱动程序)的创建必须使用root身份键入以下 

上面这句命令中,c表示创建一个字符设备,该设备的主驱动号major number为60,次驱 

对于这个驱动,为了在内核空间将其链接到对应的/dev下的文件,需要使用 
在安装模块时它以下面的方式被调用:

注意kmalloc函数的使用。这个函数在内核空间中分配一块用于设备驱动缓冲区的内存。 
它的使用方法与著名的malloc函数类似。最后,如果注册主驱动号失败或分配内存失败, 

}移除驱动时还原一个干净的内核,在这个函数中同时释放了驱动的缓冲区。

关信息;一个file结构,该结构中包括操作文件的多个不同函数。但本文并不对这些函数 

当一个文件被打开,通常需要初始化驱动变量或重新设置这个设备。但在这个例子中这 

当一个文件关闭,通常需要释放已使用的内存和任何打开文件时关链到的变量。但是,同 
样的因为本例十分简单,这些工作这里都没有做。

员。这里它对应memory_read函数。它的参数有:一个file结构;一个缓冲区buf,用户 
空间从该缓冲区中读数据;一个计数器count记录传输的字节;最后,还有f_pos,用来 
指示从文件的哪里开始读取。

}置f_pos同时也会改变。如果从文件开头读起,f_pos会以1递增,并且返回已正确 
读到的字节数,即1。如果不是从文件开头读起,文件的结束标志0将被返回,因为文件中 

加入之前的所有代码后,便组成了完整的memory驱动memory.c:

在使用本驱动之前,当然需要先编译它,方法与前面类似。加载模块:

很方便即可取消对设备的保护:

如果一切顺利,将有一个设备/dev/memory存在,且可以将其中写字符串或字符,它将存 
储字符串或多个字符中的最后一个。可以像这样来操作:

使用cat来检查这个设备的内容:

已存的字符不会改变,除非再写入覆盖它或这个模块被卸载。

生成文件中有 memory.ko, 该文件即要使用的目标模块

可见其为最后写入的数据。

我要回帖

更多关于 linux字符设备和块设备 的文章

 

随机推荐