LDD 第三章 (二.一个简化过的超级简单的scull实现)
看LDD到第三章,发现example里面包含了前几章的内容.于是重新实现了只依赖第三章中所提及的部分.顺道加了实现流程的注释,有助于理解整个实现的过程.
scull.h
View Code
#ifndef _SCULL_H_
#define _SCULL_H_
#ifndef SCULL_MAJOR
#define SCULL_MAJOR 0 /*默认的动态主设备号*/
#endif /* SCULL_MAJOR */
#ifndef SCULL_QUANTUM
#define SCULL_QUANTUM 4000
#endif /* SCULL_QUANTUM */
#ifndef SCULL_QSET
#define SCULL_QSET 1000
#endif /* SCULL_QSET */
#define SCULL_DEBUG /* 开启测试 */
#undef PDEBUG
#ifdef SCULL_DEBUG
#ifdef __KERNEL__
#define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: " fmt, ## args)
#else
#define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
#endif /* __KERNEL__ */
#else
#define PDEBUG(fmt, args...)
#endif /* SCULL_DEBUG */
#undef PDEBUGG
#define PDEBUGG(fmt, args...)
struct scull_qset {
void **data;
struct scull_qset *next;
};
struct scull_dev {
struct scull_qset *data;
int quantum;
int qset;
unsigned long size;
unsigned int access_key;
struct semaphore sem;
struct cdev cdev;
};
/*注意,从设备不给外部看见和修改*/
extern int scull_major;
extern int scull_quantum;
extern int scull_qset;
#endif /* _SCULL_H_ */
scull.c
View Code
/* 内核版本3.0.0-12,也许会与书上略有差异 */
#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/fcntl.h> /* O_ACCMODE */
#include <linux/cdev.h>
#include <asm/uaccess.h> /* copy_*_user */
#include "scull.h" /* local definitions */
/* 第1步:设置设备的各个基本参数 */
int scull_major = SCULL_MAJOR;
int scull_minor = 0;
int scull_quantum = SCULL_QUANTUM;
int scull_qset = SCULL_QSET;
/* 第2步:设置设备作为模块加载时改变参数的宏 */
module_param(scull_major, int, S_IRUGO);
module_param(scull_minor, int, S_IRUGO);
module_param(scull_quantum, int, S_IRUGO);
module_param(scull_qset, int, S_IRUGO);
/* 第3步:设置作者和许可 */
MODULE_AUTHOR("Adolph");
MODULE_LICENSE("Dual BSD/GPL");
/*首先设计可以在设备上进行的操作*/
/* 第6步:根据文件操作的需求,占位函数,再实现 */
/* scull_trim负责清楚scull_dev占用的内存
* @dev: dev是一个scull_dev结构体指针
*
* return: 返回0为成功
*/
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++) {
kfree(dptr->data[i]);
}
kfree(dptr->data);
dptr->data = NULL;
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
/* scull_follow 找到scull_dev中指定的量子集
* @dev: scull_dev设备结构体指针
* @n: 量子集在scull_dev中的位置
*
* return: 成功返回对应量子集结构指针,失败返回NULL.
*/
struct scull_qset
*scull_follow(struct scull_dev *dev, int n)
{
struct scull_qset *qs = dev->data;
/* 如果必要的话,申请第一个量子集结构 */
if (!qs) {
qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
/* 注意,任何时候分配内存都可能失败,使用之前一定要检查 */
if (qs == NULL) {
PDEBUG("scull_follow_if_fail\n");
return NULL;
}
memset(qs, 0, sizeof(struct scull_qset));
}
/* 创建其他的量子集 */
while (n--) {
if (!qs->next) {
qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if (qs->next == NULL) {
PDEBUG("scull_follow_n_%d\n", n);
return NULL;
}
memset(qs->next, 0, sizeof(struct scull_qset));
}
qs = qs->next;
}
return qs;
}
/* 第5步:根据需要实现的函数先占位,再实现 */
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;
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset;
int item, rest, s_pos, q_pos;
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;
/* 查找listitem中量子集的索引以及量子偏移位 */
item = (long)*f_pos / itemsize; /* 常规类型才能参与计算 */
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;
/* 轮询链表直到真确的位置 */
dptr = scull_follow(dev, item);
if (dptr == NULL || !dptr->data || !dptr->data[s_pos])
goto out;
/* 只读取到当前量子的结尾 */
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; /* 用来作为goto out的返回值 */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
/* 查找listitem中量子集的索引以及量子偏移位 */
item = (long)*f_pos / itemsize; /* 常规类型才能参与计算 */
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;
dptr = scull_follow(dev, item);
if (dptr == NULL) {
PDEBUG("scull_follow_fail\n");
goto out;
}
if (!dptr->data) {
dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
if (!dptr->data) {
PDEBUG("km_dptr->data_fail\n");
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]) {
PDEBUG("km_dptr->data[s_pos]_fail\n");
goto out;
}
}
/* 只写到当前量子末尾 */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_from_user(dptr->data[s_pos] + q_pos, buf, count)) {
retval = -EFAULT;
PDEBUG("copy_fail\n");
goto out;
}
*f_pos += count;
retval = count;
PDEBUG("%d", count);
/* 更新scull_dev数据大小记录 */
if (dev->size < *f_pos)
dev->size = *f_pos;
out:
up(&dev->sem);
return retval;
}
/*
*
*/
loff_t
scull_llseek(struct file *filp, loff_t off, int whence)
{
struct scull_dev *dev = filp->private_data;
loff_t newpos;
switch(whence) {
case SEEK_SET:
newpos = off;
break;
case SEEK_CUR:
newpos = filp->f_pos + off;
break;
case SEEK_END:
newpos = dev->size + off;
break;
default:
return -EINVAL;
}
if (newpos < 0)
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
int
scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev;
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev;
/* 只写打开时清空设备 */
if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
scull_trim(dev); /*忽略错误处理*/
up(&dev->sem);
}
return 0;
}
int
scull_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 第4步:定义文件操作结构 */
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.open = scull_open,
.release = scull_release,
};
/* 第7步:设置设备对象指针,设备在初始化时创建 */
struct scull_dev *scull_devices;
/* 第10步:为初始化和清除中用到的函数占位,并实现 */
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 "Error %d adding scull%d", err, index);
}
/* 第9步:实现初始化和释放函数*/
void
scull_cleanup_module(void)
{
dev_t devno = MKDEV(scull_major, scull_minor);
/* 去掉我们字符设备的入口 */
if (scull_devices) {
scull_trim(scull_devices);
cdev_del(&scull_devices->cdev);
kfree(scull_devices);
}
unregister_chrdev_region(devno, 1);
}
int
scull_init_module(void)
{
int result;
dev_t dev = 0;
/* 获得多个用于工作的从设备号,除非在加载时才能请求一个动态的主设备号 */
if(scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, 1, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, 1, "scull");
scull_major = MAJOR(dev);
}
if (result <0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
/* 申请设备--我们不能一直持有他们,我们可以在加载的时候指定设备数量 */
scull_devices = kmalloc(sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices) {
result = -ENOMEM;
goto fail;
}
memset(scull_devices, 0, sizeof(struct scull_dev));
/* 初始化设备 */
scull_devices->quantum = scull_quantum;
scull_devices->qset = scull_qset;
sema_init(&scull_devices->sem, 1);
scull_setup_cdev(scull_devices, 0);
return 0;
fail:
scull_cleanup_module();
return result;
}
/* 第8步:定义模块初始化和释放函数 */
module_init(scull_init_module);
module_exit(scull_cleanup_module);
app-scull.c
View Code
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
int main()
{
int fp0;
char Buf[4096];
/* 初始化Buf */
strcpy(Buf,"Scull is char dev!");
printf("BUF: %s\n",Buf);
/* 打开设备文件 */
fp0 = open("/dev/scull0", O_RDWR);
if (fp0 < 0)
{
printf("Open scull Error!\n");
return -1;
}
/* 写入设备 */
strcpy(Buf,"Scull write!");
write(fp0, Buf, sizeof(Buf));
/* 重新定位文件位置 */
lseek(fp0,0,SEEK_SET);
/* 清除Buf */
strcpy(Buf,"Buf is NULL!");
/* 读出设备 */
read(fp0, Buf, sizeof(Buf));
/* 检测结果 */
printf("BUF: %s success\n",Buf);
return 0;
}
makefile
View Code
obj-m := scull.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
shell
View Code
#major在/sys/modules/scull/parameter/major中查找
mknod /dev/scull0 c $(major) 0
chmod 666 /dev/scull0