一个简单的Linux设备驱动程序

来源:互联网 发布:linux 进程状态 编辑:程序博客网 时间:2024/05/03 00:55

我所理解的Linux设备驱动程序工作原理:

       在Linux中,一切设备都看做文件来处理,在用户程序需要对硬件设备进行数据读写操作时,可以像读写一般磁盘文件的操作方式一样,通过open,write,read等系统调用来进行数据读写操作。在用户程序使用这些系统调用时,操作系统通过一个名为struct file_operations的数据结构来找到驱动程序中与系统调用对应函数,然后把系统调用应实现的功能交给该函数。所以在驱动程序的设计中,主要的工作就是实现struct file_operations结构中的函数功能(这样就把系统调用与驱动程序关联起来了),然后通过内核模块动态加载驱动程序到系统内核中,这样就一个简单的驱动程序就实现了。

 

主设备号:用来标识设备对应的驱动程序


次设备号:次设备号由内核使用,用于正确确定设备文件所指的设备(指向哪一个硬件设备)

在内核中,dev_t类型用来保存设备编号(包括主设备编号与次设备编号),MAJOR()与MINOR()宏可以获得主设备号与次设备号。

 

编写Linux设备驱动程序的一般流程:

1.  从操作系统获取设备号(主设备号与次设备号)

2.  向操作系统注册设备驱动。

3. 把系统调用open,write,read等统通过struct file_operations结构关联到设备驱动程序,让设备驱动程序来完成特定的功能。

4.  向操作系统注销设备驱动。

5.  释放从系统获得的设备号。


下面是自己写的一个完整的字符设备驱动程序,通过模块加载即可

 

#include <linux/module.h>#include <linux/init.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/sched.h>#include <linux/fcntl.h>#include <linux/kdev_t.h>#include <linux/kernel.h>#include <linux/errno.h>#include <linux/cdev.h>#include <linux/mm.h>#include <linux/slab.h>#include <asm/io.h>#include <asm/system.h>#include <asm/uaccess.h>MODULE_LICENSE("GPL");const int MAXSIZE = 4096;//自定义自己的字符设备struct Char_dev{char buff[4096];unsigned long size;struct cdev c_dev;};int myOpen(struct inode *, struct file *);ssize_t myRead(struct file *, __user char *, size_t, loff_t *);ssize_t myWrite(struct file *, const __user char *, size_t, loff_t *);loff_t myLlseek(struct file*, loff_t, int);int myRelease(struct inode *, struct file *);//初始化 file_operations结构体,用来关联系统调用函数struct file_operations myFops = {.owner = THIS_MODULE,.open = myOpen,.release = myRelease,.read = myRead,.write = myWrite,.llseek = myLlseek,};dev_t dev_number; //设备号struct Char_dev myDev;//模块的初始化与清理部分************************************************static int __init myDevInit(void){//1. 分配设备编号int ret;ret = alloc_chrdev_region(&dev_number, 0, 1, "myCharDev");if (ret < 0){printk(KERN_ALERT "分配设备号失败!\n");return ret;}//2. 分配并注册字符设备//初始化cdev 结构cdev_init(&(myDev.c_dev), &myFops);myDev.c_dev.ops = &myFops;myDev.c_dev.owner = THIS_MODULE;//注册字符设备ret = cdev_add(&(myDev.c_dev), dev_number, 1);if (ret){printk(KERN_ALERT "注册设备失败!\n");return ret;}return 0;}static void __exit myDevExit(void){//1. 移除设备cdev_del(&(myDev.c_dev));//2. 释放设备号unregister_chrdev_region(dev_number, 1);}module_init(myDevInit);module_exit(myDevExit);//实现 open 函数****************************************************int myOpen(struct inode *p_inode, struct file *p_file){struct Char_dev *p_myDev;p_myDev = container_of(p_inode‐>i_cdev, struct Char_dev, c_dev);p_file‐>private_data = p_myDev;p_file‐>f_pos = 0;return 0;}//实现 release 函数‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐int myRelease(struct inode *P_inode, struct file *p_file){return 0;}//实现 read 函数*****************************************************ssize_t myRead(struct file *p_file, char __user *buff, size_t count, loff_t *f_pos){int err;struct Char_dev *p_myDev;p_myDev = p_file‐>private_data;printk(KERN_ALERT "读取数据前设备文件大小:%lu Byte\n", p_myDev‐>size);printk(KERN_ALERT "读取数据前文件指针位置:%lld\n", *f_pos);if (p_myDev‐>size == 0){printk(KERN_ALERT "设备文件无数据!\n");return ‐1;}if (*f_pos >= p_myDev‐>size){printk(KERN_ALERT "已到文件末尾!\n");*f_pos = 0;return ‐2;}if (count > p_myDev‐>size ‐ (*f_pos))count = p_myDev‐>size ‐ (*f_pos);err = copy_to_user(buff, (void *)(p_myDev‐>buff + (*f_pos)), count);if (err){printk(KERN_ALERT "读取数据失败!\n");return ‐EFAULT;}elseprintk(KERN_ALERT "读取数据:%d Byte\n", count);*f_pos += count;printk(KERN_ALERT "读取数据后设备文件大小:%lu Byte\n", p_myDev‐>size);printk(KERN_ALERT "读取数据后文件指针位置:%lld\n", *f_pos);return count;}//实现 write 函数 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ssize_t myWrite(struct file *p_file, const char __user *buff, size_t count, loff_t *f_pos){int err;struct Char_dev *p_myDev;p_myDev = p_file‐>private_data;printk(KERN_ALERT "写入数据前设备文件大小:%lu Byte\n", p_myDev‐>size);printk(KERN_ALERT "写入数据前文件指针位置:%lld\n", *f_pos);if ((*f_pos) >= MAXSIZE)if (count + (*f_pos) > MAXSIZE)count = MAXSIZE ‐ (*f_pos);err = copy_from_user(p_myDev‐>buff + (*f_pos), buff, count);if (err){printk(KERN_ALERT "写入数据失败!\n");return ‐EFAULT;}elseprintk(KERN_ALERT "写入数据:%d Byte\n", count);*f_pos += count;if ((*f_pos) > p_myDev‐>size)p_myDev‐>size = (*f_pos);printk(KERN_ALERT "写入数据后设备文件大小:%lu Byte\n", p_myDev‐>size);printk(KERN_ALERT "写入数据后文件指针位置:%lld\n", *f_pos);return count;}//实现文件定位 llseek 函数‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐loff_t myLlseek(struct file *p_file, loff_t offset, int modu){loff_t newpos;struct Char_dev *p_myDev = p_file‐>private_data;switch (modu){case SEEK_CUR:newpos = p_file‐>f_pos + offset;break;case SEEK_END:newpos = p_myDev‐>size ‐ 1 + offset;break;case SEEK_SET:newpos = offset;break;default:newpos = ‐1;break;}if (p_myDev‐>size < newpos)newpos = p_myDev‐>size;p_file‐>f_pos = newpos;printk(KERN_ALERT "文件位置设为:%lld", newpos);return newpos;}