linux字符设备驱动程序怎么写
摘要:linux设备驱动程序第三版第三章笔记
1.scull:simple character utility for loading localities.
2.scull0-scull3: 4个global and persistent设备,映射到的物理内存区是不会消失的。
scullpipe0-scullpipe3:4个FIFO设备,一个进程读一个进程写。
3.主次编号:
3.1
major number标志了设备相联系的driver。
minor number用来决定引用了哪个设备。
3.2 设备编号内部表示:
dev_t类型<linux/types.h>中含有设备编号
获得dev_t的主次编号,使用<linux/kdev_t.h>中的:
MAJOR(dev_t dev); MINOR(dev_t dev);
将主次编号转换为dev_t使用:
MKDEV(int major, int minor);
3.3分配和释放设备编号:
头文件<linux/fs.h>
int register_chrdev_region(dev_t first, unsigned int count, char *name);
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
//动态分配,一旦分配了,可以在/proc/devices中读取
void unregister_chrdev_region(dev_t first, unsigned int count);
3.4主编号的动态分配:
一旦主编号动态分配了,就可以到/proc/device中来读取主编号,可以使用一个script来代替调用insmod,在insmod以后创建special files。
下面就是脚本scull_load:
#!/bin/sh module="scull" device="scull" mode="664" # invoke insmod with all arguments we got # and use a pathname, as newer modutils don't look in . by default /sbin/insmod ./$module.ko $* || exit 1 # remove stale nodes rm -f /dev/${device}[0-3] major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices) mknod /dev/${device}0 c $major 0 mknod /dev/${device}1 c $major 1 mknod /dev/${device}2 c $major 2 mknod /dev/${device}3 c $major 3 # give appropriate group/permissions, and change the group. # Not all distributions have staff, some have "wheel" instead. group="staff" grep -q '^staff:' /etc/group || group="wheel" chgrp $group /dev/${device}[0-3] chmod $mode /dev/${device}[0-3]
另外还有一个脚本scull_unload来清理/dev目录并去除模块。
分配主编号的示例代码:
if (scull_major) { dev = MKDEV(scull_major, scull_minor); result = register_chrdev_region(dev, scull_nr_devs, "scull"); } else { result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull"); scull_major = MAJOR(dev); } if (result < 0) { printk(KERN_WARNING "scull: can't get major %d\n", scull_major); return result; }
4.file_operation:
file_operation结构定义在<linux/fs.h>是一个函数指针集合。每个打开文件用file指针指向,包含一个称为f_op的成员指向file_operation结构。这些函数负责实现系统调用。
当parameter包含__user这样的字符串的时候,这是一种documentation,表示指向的是一个user-space地址。例如:char __user *。
文件操作函数有许多,scull设备驱动仅仅实现相对重要的函数:
struct file_operations scull_fops = { .owner = THIS_MODULE, .llseek = scull_llseek, .read = scull_read, .write = scull_write, .ioctl = scull_ioctl, .open = scull_open, .release = scull_release, };
5.struct file:(代表打开文件描述符)
struct file 定义在<linux/fs.h>,它与用户空间FILE没有任何关系,它由内核在open时创建,并传递给在文件上操作的任何函数,直至关闭,最后释放这个数据结构。
内核中struct file的指针通常是file 或者filp。
其中重要成员:
mode_t f_mode;
struct file_operations *f_op;
void *private_data;
loff_t f_pos;
struct dentry *f_dentry;
6.inode(表示文件)
inode结构由内核在内部用来表示文件,与file不同,可以有许多struct file指针指向一个inode,但是inode是唯一的。
inode有大量文件信息,但是只有两个成员对于编写驱动有用:
1)dev_t i_rdev; 包含实际设备编号
2)struct cdev * i_cdev; 代表字符设备。
unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode);
7.字符设备注册
内核中使用struct cdev来代表字符设备,<linux/cdev.h>中包含了这个结构和它相关连的函数。下面的
struct cdev *my_cdev = cdev_alloc(); my_cdev->ops = &my_fops; //----实际上SCULL用的是特定的结构,如下------------------- void cdev_init(struct cdev *cdev, struct file_operations *fops); //添加字符设备 int cdev_add(struct cdev *dev, dev_t num, unsigned int count); //去除字符设备 void cdev_del(struct cdev *dev);
scull设备使用一个stuct scull_dev类型的结构来表示每个设备:
1 struct scull_dev { 2 struct scull_qset *data; /* Pointer to first quantum set */ 3 int quantum; /* the current quantum size */ 4 int qset; /* the current array size */ 5 unsigned long size; /* amount of data stored here */ 6 unsigned int access_key; /* used by sculluid and scullpriv */ 7 struct semaphore sem; /* mutual exclusion semaphore */ 8 struct cdev cdev; /* Char device structure */ 9 };
上述结构初始化过程如下:
static void scull_setup_cdev(struct scull_dev *dev, int index) { int err, devno = MKDEV(scull_major, scull_minor + index); cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; err = cdev_add (&dev->cdev, devno, 1); /* Fail gracefully if need be */ if (err) printk(KERN_NOTICE "Error %d adding scull%d", err, index); }
8.open和release:
open 方法的原型是:
int (*open)(struct inode *inode, struct file *filp);
1)确定打开的是哪个设备:
a.通过INODE参数:inode参数成员i_cdev包含cdev,转化成scull_dev类型(如下dev)。
struct scull_dev *dev; /* device information */ dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; /* for other methods */ |
b.查看inode结构的次编号,如果是用register_chrdev注册的设备,就必须用这种方法。
最终,scull_open的代码如下:
int scull_open(struct inode *inode, struct file *filp) { struct scull_dev *dev; /* device information */ dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; /* for other methods */ /* now trim to 0 the length of the device if open was write-only */ if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { scull_trim(dev); /* ignore errors */ } return 0; /* success */ }
2) release 方法:
目的:释放open分配在filp->private_data中的任何东西,在最后的close关闭设备。
int scull_release(struct inode *inode, struct file *filp) { return 0; }
9.read和write:
ssize_t read(struct file *filp, char __user *buff,size_t count, loff_t *offp); ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; /* the first listitem */ int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; /* how many bytes in the listitem */ int item, s_pos, q_pos, rest; ssize_t retval = 0; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; if (*f_pos >= dev->size) goto out; if (*f_pos + count > dev->size) count = dev->size - *f_pos; /* find listitem, qset index, and offset in the quantum */ item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; /* follow the list up to the right position (defined elsewhere) */ dptr = scull_follow(dev, item); if (dptr == NULL || !dptr->data || ! dptr->data[s_pos]) goto out; /* don't fill holes */ /* read only up to the end of this quantum */ if (count > quantum - q_pos) count = quantum - q_pos; if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) { retval = -EFAULT; goto out; } *f_pos += count; retval = count; out: up(&dev->sem); return retval; }
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; int item, s_pos, q_pos, rest; ssize_t retval = -ENOMEM; /* value used in "goto out" statements */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* find listitem, qset index and offset in the quantum */ item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; /* follow the list up to the right position */ dptr = scull_follow(dev, item); if (dptr == NULL) goto out; if (!dptr->data) { dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL); if (!dptr->data) goto out; memset(dptr->data, 0, qset * sizeof(char *)); } if (!dptr->data[s_pos]) { dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); if (!dptr->data[s_pos]) goto out; } /* write only up to the end of this quantum */ if (count > quantum - q_pos) count = quantum - q_pos; if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) { retval = -EFAULT; goto out; } *f_pos += count; retval = count; /* update the size */ if (dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return retval; }
===============================分割线================================================================
一整天就搞了这麽个东西,做笔记真是个累人的事情,要提高效率,感觉自己特别急躁,可能是没有分派好任务的缘故。
作业很多,压力很大,但我却并没有尽全力,反而玩游戏什么的浪费了不少时间。sign!绝对不能这样了。
posted on 2012-05-14 21:09 Rambo.Wang 阅读(1396) 评论(0) 编辑 收藏 举报