本文代码取自<Linux设备驱动程序>第三版
通过linux字符设备驱动程序一系列文章,将实现一个scull字符设备驱动程序。更详细的内容可以参考原书,本文的目的在于讲解一些关键部分。
scull设备是基于内存的设备,每个设备都含有一个链表,链表中的每个节点是一个指针数组。数组的大小用qset表示,数组中的每个指针指向一块内存,这块内存的大小用quantum表示。
本节主要实现2个功能:
1.分配设备号
主次设备号唯一标识一个设备,主设备号标识设备对应的驱动程序。我们将创建scull0~scull3 3个字符设备,这些设备的主设备号相同,共用一个驱动程序。
2.注册设备并关联具体的文件操作(file_operations)
注册设备是通过struct cdev(结构定义在linux/cdev.h中)这个结构完成,这个结构是内核与字符设备驱动程序的接口。
通过注册设备将指定主次设备号的设备文件与具体的文件操作函数指针(file_operations)相关联,这样我们对具体设备文件的操作就转移到我们驱动程序中的具体函数上来了。
文件操作函数现在只是简单的打印次设备号和函数名,通过打印次设备号验证是我们操作的字符设备。因为主设备号都一样,所以只打印次设备号。
设备的次设备号是VFS层自动包含在inode中的,并在open设备文件时传入,我们要做的只是将次设备号取出并放在filp->private_data中。这样我们就能在具体的文件操作中区分设备了。
这样就达到了多个字符设备共用同一个驱动程序的目的。
下面结合代码和注释详细说明:
scull.h:
#ifndef _SCULL_H
#define _SCULL_H
#ifndef SCULL_MAJOR
#define SCULL_MAJOR 0 /*通过这些宏可以控制在编译时指定主次设备号和设备的个数*/
#endif
#ifndef SCULL_NR_DEVS
#define SCULL_NR_DEVS 4
#endif
#ifndef SCULL_QUANTUM
#define SCULL_QUANTUM 4000 /*通过宏控制在编译时指定qset,quantum的大小*/
#endif
#ifndef SCULL_QSET
#define SCULL_QSET 1000
#endif
struct scull_qset{
void **data;
struct scull_qset *next;
};
struct scull_dev{
struct scull_qset *data; /*设备内存链表*/
int quantum;
int qset;
unsigned long size;
struct semaphore sem; /*设备控制信号量*/
struct cdev cdev; /*内核与字符设备的接口结构体*/
};
#endif
scull.c:
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>
#include <asm/system.h> /* cli(), *_flags */
#include <asm/uaccess.h> /* copy_*_user */
#include "scull.h" /* local definitions */
int scull_major = SCULL_MAJOR;
int scull_minor = 0;
int scull_nr_devs = SCULL_NR_DEVS; /*指定一些默认值*/
int scull_quantum = SCULL_QUANTUM;
int scull_qset = SCULL_QSET;
module_param(scull_major, int, S_IRUGO); /*模块参数,可以在安装模块时指定*/
module_param(scull_minor, int, S_IRUGO);
module_param(scull_nr_devs, int, S_IRUGO);
module_param(scull_quantum, int, S_IRUGO);
module_param(scull_qset, int, S_IRUGO);
MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet");
MODULE_LICENSE("Dual BSD/GPL");
struct scull_dev *scull_devices; /*全局指针,所有的scull_dev设备*/
loff_t scull_llseek(struct file *filp,loff_t off,int whence)
{
printk(KERN_NOTICE "[SCULL] scull_llseek for minor [%d]\r\n",(int)filp->private_data); /*只是简单的打印次设备号和具体的操作*/
return 0;
}
ssize_t scull_read(struct file *filp,char __user *buf,size_t count,
loff_t *f_pos)
{
printk(KERN_NOTICE "[SCULL] scull_read for minor [%d]\r\n",(int)filp->private_data);
return 0;
}
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
printk(KERN_NOTICE "[SCULL] scull_write for minor [%d]\r\n",(int)filp->private_data);
return count;
}
int scull_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
printk(KERN_NOTICE "[SCULL] scull_ioctl for minor [%d]\r\n",(int)filp->private_data);
return 0;
}
int scull_open(struct inode *inode, struct file *filp)
{
filp->private_data = (void *)(MINOR(inode->i_cdev->dev)); /* 简单的从inode中取出次设备号并将次设备号赋值*/
printk(KERN_NOTICE "[SCULL] scull_open for minor [%d]\r\n",(int)filp->private_data);
return 0;
}
int scull_release(struct inode *inode, struct file *filp)
{
printk(KERN_NOTICE "[SCULL] scull_release for minor [%d]\r\n",(int)filp->private_data);
return 0;
}
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,
};
/*释放scull_dev的内存*/
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next,*dptr;
int qset = dev->qset;
int i;
for(dptr = dev->data;dptr;dptr = next)
{
if(dptr->data)
{
for(i = 0;i < qset;i++)
{
if(dptr->data[i])
kfree(dptr->data[i]);
}
kfree(dptr->data);
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->qset = scull_qset;
dev->quantum = scull_quantum;
dev->data = NULL;
return 0;
}
/*模块卸载函数*/
void scull_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(scull_major,scull_minor);
if(scull_devices)
{
for(i = 0;i < scull_nr_devs;i++)
{
scull_trim(&scull_devices[i]);
cdev_del(&scull_devices[i].cdev); /*删除设备*/
}
kfree(scull_devices);
}
unregister_chrdev_region(devno,scull_nr_devs);
}
/*安装scull_dev,将scull_dev驱动程序与设备号建立联系。*/
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); /*添加设备,与指定主次设备号的设备文件相关联*/
if(err)
{
printk(KERN_NOTICE "Err %d adding scull %d\n",err,index);
}
}
int scull_init_module(void)
{
int result,i;
dev_t dev = 0;
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_INFO "[SCULL] can't get major %d\n",scull_major);
return result;
}
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev),GFP_KERNEL);
if(!scull_devices)
{
result = -ENOMEM;
goto fail;
}
memset(scull_devices,0,scull_nr_devs * sizeof(struct scull_dev));
for(i = 0;i < scull_nr_devs;i++)
{
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
init_MUTEX(&scull_devices[i].sem);
scull_setup_cdev(&scull_devices[i],i);
}
return 0;
fail:
scull_cleanup_module();
return result;
}
module_init(scull_init_module);
module_exit(scull_cleanup_module);
验证:
1.首先编译scull.ko
[root@localhost scull]# make all
make -C /usr/src/kernels/linux-2.6.32-220.el6.i686 M=/home/zhangxa/Linux/kernel-prog/scull modules
make[1]: Entering directory `/usr/src/kernels/2.6.32-220.el6.i686'
CC [M] /home/zhangxa/Linux/kernel-prog/scull/scull.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/zhangxa/Linux/kernel-prog/scull/scull.mod.o
LD [M] /home/zhangxa/Linux/kernel-prog/scull/scull.ko.unsigned
NO SIGN [M] /home/zhangxa/Linux/kernel-prog/scull/scull.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.32-220.el6.i686'
2.安装ko
insmod scull.ko
3.查看动态分配的主设备号
[root@localhost scull]# cat /proc/devices | grep scull
249 scull
[root@localhost scull]#
4.创建4个设备文件
因为主设备号是249,我们创建次设备号为0~3的3个设备文件
[root@localhost scull]# mknod /dev/scull0 c 249 0
[root@localhost scull]# mknod /dev/scull1 c 249 1
[root@localhost scull]# mknod /dev/scull2 c 249 2
[root@localhost scull]# mknod /dev/scull3 c 249 3
5.通过cat命令确认设备注册成功
[root@localhost scull]# cat /dev/scull0
[root@localhost scull]# dmesg | grep -i scull
[SCULL] scull_open for minor [0]
[SCULL] scull_read for minor [0]
[SCULL] scull_release for minor [0]
同理,我们操作 cat/dev/scull1,可以看到打印出的次设备号变成了1
[root@localhost scull]# cat /dev/scull1
[root@localhost scull]# dmesg | grep -i scull
SCULL] scull_open for minor [1]
[SCULL] scull_read for minor [1]
[SCULL] scull_release for minor [1]
还可以通过echo命令测试write函数
[root@localhost scull]# echo "1111" > /dev/scull3
[root@localhost scull]# dmesg | grep -i scull
[SCULL] scull_open for minor [3]
[SCULL] scull_write for minor [3]
[SCULL] scull_release for minor [3]