LED驱动
- 格式:doc
- 大小:89.00 KB
- 文档页数:13
Linux系统下的LED驱动(X86平台)
1.Linux设备驱动与整个软硬件系统的关系
如图所示,除网络设备外,字符设备与块设备都被映射到Linux文件系统的文件和目录,通过文件系统的系统调用接口open()、write()、read()、close()等函数即可访问字符设备和块设备。所有的字符设备和块设备都被统一地呈现给用户。块设备比字符设备复杂,在它上面会首先建立一个磁盘/Flash文件系统,如FAT、Ext3、YAFFS、JFFS等。
2. Linux系统下的LED驱动
硬件环境:PC104
软件环境:使用的系统为红旗Linux Notebook 5.0(内核版本Linux 2.6.16.9-9smp SMP
PENTIUM gcc-3.4) 编译的内核为2.6.16(注意版本的匹配,前面三位要一致)
用户应用程序
Linux系统调用接口
硬件 C库
进程管理
内存管理 Linux文件系统
字符设备驱动 磁盘/Flash文件系统
块设备驱动 套接字
TCP/IP
网络设备驱动 操作系统 Linux提供了这样一种机制——模块。模块具有以下特点:
1 模块本身不被编译进内核
2 模块一旦被加载,它就和内核中的其他部分完全一样。
先看一个最简单的内核模块“Hello World”,代码如下所示
#include
#include
MODULE_LICENSE(“Dual BSD/GPL”);
static int hello_init(void)
{
printk(KERN_ALERT “Hello World enter\n”);
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT “Hello World exit\n”);
}
module_init(hello_init);
module_eixt(hello_exit);
这个最简单的内核模块只包含内核加载函数、卸载函数和对GPL许可权限的声明以及一些描述信息。编译它会产生hello.ko目标文件,通过“insmod ./hello.ko”命令可以加载它,通过“rmmod hello”命令可以卸载它,加载时输出“Hello World enter”,卸载时输出“Hello
World exit”。
加载完成后可以用modinfo hello.ko命令查看模块的信息,包括作者、模块的说明。模块所支持的参数以及编译这个模块用到的环境信息。
从上面这个最简单的例子中可以看出一个Linux内核模块主要由以下几个部分组成。
模块加载函数
模块卸载函数
模块许可证声明
前面三个部分是必须的,还有一些可选的,由于这里没有用到,所以留做以后再做说明。
模块加载函数必须以“module_init(函数名)”的形式被指定。模块卸载函数必须以“module_exit(函数名)”的形式来指定。
2.1 Linux字符设备驱动结构
2.1.1. cdev结构体
在Linux2.6内核中使用cdev结构体来描述字符设备,cdev结构体的定义代码如下:
struct cdev
{
struct kobject kobj;
struct module *owner;
struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
}; cdev结构体
cdev结构体的dev_t成员定义了设备号,为32位,其中高12为主设备号,低20位为次设备号。使用下面宏可以从dev_t获得主设备号和次设备号。
MAJOR(dev_t dev)
MINOR(dev_t dev)
而使用下列宏则可以通过主设备号和次设备号生成dev_t
MKDEV(int major,int minor)
cdev结构体的另外一个重要成员file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数。
Linux2.6 内核提供了一组函数用于操作cdev结构体,就使用了的函数说明一下
void cdev_init(struct cdev*,struct file_operations *); 初始化cdev的成员,并建立cdev和file_operations之间的连接
int cdev_add(struct cdev*,dev_t,unsigned); 模块加载函数中
void cdev_del(struct cdev*); 模块卸载函数中
这两个函数是向系统添加和删除一个cdev,完成字符设备的注册和注销。
2.1.2. 分配和释放设备号
在调用cdev_add()函数向系统注册字符设备之前,应首先调用register_chrdev_region()或者alloc_chrdev_region()函数向系统申请设备号,这两个函数原型如下:
int register_chrdev_region(dev_t from,unsigned count,const char*name);
用于已知设备的设备号
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char*name);
向系统动态申请未被占用的设备号的情况。
相反地,在调用dev_del()函数从系统注销字符设备后,unregister_chrdev_region()应该被调用以释放原先申请的设备号,这个函数的原型如下:
void unregister_chrdev_region(dev_t from,unsigned count);
2.1.3. file_operations 结构体
file_operations 结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open(),write(),read(),close()等系统调用时 最终被调用。
File_operations的数据结构如下:
struct module *owner
第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 中定义的宏.
loff_t (*llseek) (struct file *, loff_t, int);
llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示.
如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以
-EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).
ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);
初始化一个异步读 -- 可能在函数返回前不结束的读操作. 如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
初始化设备上的一个异步写.
int (*readdir) (struct file *, void *, filldir_t);
对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.
unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写.
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl
方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.
int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.
int (*open) (struct inode *, struct file *);
尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
int (*flush) (struct file *);
flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.
int (*release) (struct inode *, struct file *);
在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.
int (*fsync) (struct file *, struct dentry *, int);