1、不需要重新启动内核,这使驱动调试效率大大提高。我们的实验在PC机和UP-NETARM2410-S上都可以运行,编译时使用不同的编译器就可以了。1.驱动程序与应用程序的区别应用程序一般有一个main函数,从头到尾执行一个任务;驱动程序却不同,它没有main函数,通过使用宏module_init(初始化函数名); 将初始化函数加入内核全局初始化函数列表中,在内核初始化时执行驱动的初始化函数,从而完成驱动的初始化和注册,之后驱动便停止等待被应用软件调用。驱动程序中有一个宏moudule_exit(退出处理函数名)注册退出处理函数。它在驱动退出时被调用。应用程序可以和GLIBC库连接,因此可以包含标准
2、的头文件,比如 ,在驱动程序中是不能使用标准C库的,因此不能调用所有的C库函数,比如输出打印函数只能使用内核的printk函数,包含的头文件只能是内核的头文件,比如。2.内核版本与编译器的版本依赖当模块与内核链接时,insmod会检查模块和当前内核版本是否匹配,每个模块都定义了版本符号_module_kernel_version,这个符号位于模块文件的ELF头的.modinfo段中。只要在模块中包含 rd/0crw-rw-rw- 1 root root 5, 0 Jan 1 00:00 ttycrw- 1 root root 4, 64 Jan 1 00:11 ttyS0crw- 1 root
3、 root 4, 65 Jan 1 00:00 ttyS1crw-r-r- 1 root root 1, 9 Jan 1 00:00 urandomcrw-rw-rw- 1 root root 1, 5 Jan 1 00:00 zero4.设备文件设备类型、主次设备号是内核与设备驱动程序通信时所使用的,但是对于开发应用程序的用户来说比较难于理解和记忆,所以Linux使用了设备文件的概念来统一对设备的访问接口,在引入设备文件系统(devfs)之前Linux将设备文件放在/dev目录下,设备的命名一般为设备文件名数字或字母表示的子类,例如/dev/hda1、/dev/hda2等。在Linux2.4
4、内核中引入了设备文件系统(devfs),所有的设备文件作为一个可以挂装的文件系统,这样就可以被文件系统进行统一管理,从而设备文件就可以挂装到任何需要的地方。命名规则也发生了变化,一般将主设备建立一个目录,再将具体的子设备文件建立在此目录下。比如在UP-NETARM2410-S中的MTD 设备为:/dev/mtdblock/0。5.设备驱动程序接口通常所说的设备驱动程序接口是指结构file_operations,它定义在include/linux/fs.h中。 file_operations数据结构说明struct file_operations struct module *owner; lo
5、ff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl)
6、(struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync);
7、int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct
8、 page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);#ifdef MAGIC_ROM_PTR int (*romptr) (struct file *, struct vm_area_struct *);#endif /* MAGIC_ROM_PTR */;file_operations结构是整个Linux内核的重要数据结构,它也是file、inode结
9、构的重要成员,表5.1.1中分别说明结构中主要的成员:表5.1.1 file_operations结构Ownermodule的拥有者。Llseek重新定位读写位置。Read从设备中读取数据。Write向字符设备中写入数据。Readdir只用于文件系统,对设备无用。Ioctl控制设备,除读写操作外的其他控制命令。Mmap将设备内存映射到进程地址空间,通常只用于块设备。Open打开设备并初始化设备。Flush清除内容,一般只用于网络文件系统中。Release关闭设备并释放资源。Fsync实现内存与设备的同步,如将内存数据写入硬盘。Fasync实现内存与设备之间的异步通讯。Lock文件锁定,用于文件
10、共享时的互斥访问。Readv在进行读操作前要验证地址是否可读。Writev在进行写操作前要验证地址是否可写。在嵌入式系统的开发中,我们一般仅仅实现其中几个接口函数:read、write、ioctl、open、release,就可以完成应用系统需要的功能。6.file 数据结构说明struct file struct list_head f_list; struct dentry *f_dentry; struct vfsmount *f_vfsmnt; struct file_operations *f_op; atomic_t f_count; unsigned int f_flags; m
11、ode_t f_mode; loff_t f_pos; unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; struct fown_struct f_owner; unsigned int f_uid, f_gid; int f_error; unsigned long f_version; /* needed for tty driver, and maybe others */ void *private_data; /* preallocated helper kiobuf to speedup O_DIRECT */ s
12、truct kiobuf *f_iobuf; long f_iobuf_lock; file结构中与驱动相关的重要成员说明我们将struct file 结构指针定义为flip,以便于下面说明。表5.1.2 file结构中与驱动相关的成员f_mode标识文件的读写权限f_pos当前读写位置,类型为loff_t是64位的数,只能读不能写f_flag文件标志,主要用于进行阻塞/非阻塞型操作时检查f_op文件操作的结构指针,内核在OPEN操作时对此指针赋值。private_dataOpen系统调用在调用驱动程序的open方法前,将此指针值NULL,驱动程序可以将这个字段用于任何目的,一般用它指向已经分
13、配的数据,但在内核销毁file结构前要在release方法中释放内存。f_dentry文件对应的目录项结构,一般在驱动中用filp-f_dentry-d_inode访问索引节点时用到它。7.驱动接口的实现过程我们先看看实验代码框架其中, static struct file_operations demo_fops = 完成了将驱动函数映射为标准接口,devfs_registe()和register_chrdev()函数完成将驱动向内核注册。static struct file_operations demo_fops = owner: THIS_MODULE, write: demo_wri
14、te, read: demo_read, ioctl: demo_ioctl, open: demo_open, release:demo_release,上面的这种特殊表示方法不是标准C的语法,这是GNU编译器的一种特殊扩展,它使用名字对进行结构字段的初始化,它的好处体现在结构清晰,易于理解,并且避免了结构发生变化带来的许多问题。 Open方法Open方法提供给驱动程序初始化设备的能力,从而为以后的设备操作做好准备,此外open操作一般还会递增使用计数,用以防止文件关闭前模块被卸载出内核。在大多数驱动程序中Open方法应完成如下工作:1. 递增使用计数2. 检查特定设备错误。3. 如果设备是
15、首次打开,则对其进行初始化。4. 识别次设备号,如有必要修改f_op指针。5. 分配并填写filp-private_data中的数据。 Release方法与open方法相反,release 方法应完成如下功能:1. 释放由open分配的filp-private_data中的所有内容2. 在最后一次关闭操作时关闭设备3. 使用计数减一 Read和Write方法ssize_t demo_write(struct file *filp,const char * buffer, size_t count,loff_t *ppos)ssize_t demo_read(struct file *filp,
16、 char *buffer, size_t count, loff_t *ppos)read 方法完成将数据从内核拷贝到应用程序空间,write方法相反,将数据从应用程序空间拷贝到内核。对于者两个方法,参数filp是文件指针,count是请求传输数据的长度,buffer是用户空间的数据缓冲区,ppos是文件中进行操作的偏移量,类型为64位数。由于用户空间和内核空间的内存映射方式完全不同,所以不能使用象memcpy之类的函数,必须使用如下函数:unsigned long copy_to_user (void *to,const void *from,unsigned long count);un
17、signed long copy_from_user(void *to,const void *from,unsigned long count); Read的返回值1. 返回值等于传递给read系统调用的count参数,表明请求的数据传输成功。2. 返回值大于0,但小于传递给read系统调用的count参数,表明部分数据传输成功,根据设备的不同,导致这个问题的原因也不同,一般采取再次读取的方法。3. 返回值0,表示到达文件的末尾。4. 返回值为负数,表示出现错误,并且指明是何种错误。5. 在阻塞型io中,read调用会出现阻塞。 Write的返回值1. 返回值等于传递给write系统调用的c
18、ount参数,表明请求的数据传输成功。2. 返回值大于0,但小于传递给write系统调用的count参数,表明部分数据传输成功,根据设备的不同,导致这个问题的原因也不同,一般采取再次读取的方法。3. 返回值0,表示没有写入任何数据。标准库在调用write时,出现这种情况会重复调用write。错误号的定义参见5. 在阻塞型io中,write调用会出现阻塞。 ioctl方法ioctl方法主要用于对设备进行读写之外的其他控制,比如配置设备、进入或退出某种操作模式,这些操作一般都无法通过read/write文件操作来完成,比如在UP-NETARM2410-S中的SPI设备通道的选择操作,无法通过wri
19、te操作控制,这就是ioctl操作的功能。用户空间的ioctl函数的原型为:int ioctl(inf fd,int cmd,)其中的代表可变数目的参数表,实际中是一个可选参数,一般定义为:int ioctl(inf fd,int cmd,char *argp)驱动程序中定义的ioctl 方法原型为:int (*ioctl) (struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)inode 和 filp两个指针对应应用程序传递的文件描述符fd,cmd不会被修改地传递给驱动程序,可选的参数arg则无
20、论用户应用程序使用的是指针还是其他类型值,都以unsigned long的形式传递给驱动。 ioctl方法的命令编号确定由于为了防止向不该控制的设备发出正确的命令,LINUX驱动的ioctl方法中的cmd参数推荐使用唯一编号,编号方法并根据如下规则定义:编号分为4个字段:1. type(类型):也称为幻数,8位宽。2. number(号码):顺序数,8位宽。3. direction(方向):如果该命令有数据传输,就要定义传输方向,2位宽,可使用的数值:a) _IOC_NONEb) _IOC_READc) _IOC_WRITE 4. size(大小):数据大小,宽度与体系结构有关,在ARM上为1
21、4位。这些定义在中可以找到。其中还定义了一些用于构造命令号的宏:#define _IOC_NRBITS 8#define _IOC_TYPEBITS 8#define _IOC_SIZEBITS 14#define _IOC_DIRBITS 2#define _IOC_NRMASK (1 _IOC_NRBITS)-1)#define _IOC_TYPEMASK (1 _IOC_TYPEBITS)-1)#define _IOC_SIZEMASK (1 _IOC_SIZEBITS)-1)#define _IOC_DIRMASK (1 _IOC_DIRBITS)-1)#define _IOC_NRS
22、HIFT 0#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)#define _IOC_NONE 0U#define _IOC_WRITE 1U#define _IOC_READ 2U#define _IOC(dir,type,nr,size) (dir) _IOC_DIRSHIFT) | (type) _IOC_TYPESHIFT) | (nr) _IOC_NRSHIFT) | (size) _IOC_DIRSHIFT) & _IOC_DIRMASK)#define _IOC_TYPE(nr) (
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1