/***************************************************************************************************
scull2.h
空间模型:
scull_dev
{
*data----------->scull_qset /-------------------->scull_qset /-------...--->NULL
quantum { *next--------/ { *next--------/
qset **data->*quantum0 **data->*quantum0
size } *quantum1-->char[4000] } *quantum1-->char[4000]
access_key *quantum2-->char[4000] *quantum2-->char[4000]
sem *quantum3 *quantum3
cdev *quantum4-->char[4000] *quantum4-->char[4000]
... ...
} *qua.1000-->char[4000] *qua.1000-->char[4000]
量子集 量子
scull_qset是一个链表结构
一个指针指向下一个结构。
一个指针指向一个量子集。
量子集是n=1000个指针的数组,每个指针指向一块内存,此内存有m=4000字节大。一块内存即是以个量子。
策略:
写入: 若在指定位置写入数据时
若该位置还没有量子集,开辟一个量子集空间,即1000个指针的空间,初始化为NULL
若该位置也没有量子时,开辟一个量子 空间,即4000个字节的空间。有一个量子集指针指向它。
读出:
若指定位置的数据不存在,则不会读到数据。
本例子实现如下功能:
1 自动注册设备,添加节点。
2 只实现一个设备scull0。量子集大小改为100 量子大小改为400
3 实现了4个操作,借鉴ldd3的例子,去掉中断和信号量的使用,以后涉及
scull_open()
scull_release()
scull_read()
scull_write()
4 本例子使用方法:
1 将所有代码放同一目录下
2 make
3 gcc user2.c
4 insmod sucll2.ko
5 ./a.out
6 rmmod sucll2
5 查看信息 tail -f /var/log/messages
编者:张永辉 2012年10月12日
***************************************************************************************************/
#ifndef _SCULL_H_
#define _SCULL_H_
/***************************************************************************************************
常量定义
***************************************************************************************************/
#ifndef SCULL_MAJOR
#define SCULL_MAJOR 0 //dynamic major by default 缺省的主设备号
#endif
#ifndef SCULL_NR_DEVS
#define SCULL_NR_DEVS 4 // scull0 1 2 3
#endif
#ifndef SCULL_QUANTUM //量子大小,单位字节
#define SCULL_QUANTUM 400
#endif
#ifndef SCULL_QSET //量子集大小
#define SCULL_QSET 100
#endif
/***************************************************************************************************
数据结构定义
***************************************************************************************************/
struct scull_qset
{
void **data;
struct scull_qset *next;
};
struct scull_dev {
struct scull_qset *data; // Pointer to first quantum set 指向第一个 scull_qset结构
int quantum; // the current quantum size 一个量子的大小, 默认4000
int qset; // the current array size 一个量子集的个数,默认1000
unsigned long size; // amount of data stored here 保存了的数据总数
unsigned int access_key; // used by sculluid and scullpriv
struct semaphore sem; // mutual exclusion semaphore 信号量
struct cdev cdev; // Char device structure 设备
};
//**************************************************************************************************
#endif
/***************************************************************************************************
scull2.c
***************************************************************************************************/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h> /* everything... */
#include <linux/kernel.h>
#include <asm/uaccess.h> /* copy_*_user */
#include <linux/cdev.h> //cdev
#include <linux/types.h> /* size_t */
#include <linux/slab.h> /* kmalloc() */
#include <linux/device.h> //class_create
//#include <linux/moduleparam.h>
#include <linux/errno.h> /* error codes */
#include <linux/proc_fs.h>
//#include <linux/fcntl.h> /* O_ACCMODE */
//#include <linux/seq_file.h>
//#include <asm/system.h> /* cli(), *_flags */
#include "scull2.h"
MODULE_LICENSE("Dual BSD/GPL");
/***************************************************************************************************
全局变量声明
***************************************************************************************************/
dev_t scull_num; // 设备号
int scull_major = SCULL_MAJOR; //主设备号 scull.h #define=0
int scull_minor = 0; //次设备号
int scull_quantum = SCULL_QUANTUM; //量子大小 = 400
int scull_qset = SCULL_QSET; //量子集大小 = 100
struct scull_dev *scull_devices=NULL; //在 scull_init_module 分配的设备存储区
static struct class *scull_class=NULL;
/***************************************************************************************************
函数声明
***************************************************************************************************/
//装载和卸载函数
static void scull_cleanup_module(void);
static int scull_init_module(void);
//以file_operations scull_fops{}为接口的函数
static int scull_open (struct inode *node, struct file *filp);
static int scull_release(struct inode *node, struct file *filp);
static ssize_t scull_read (struct file *filp, char __user *buf, size_t count,loff_t *f_pos);
static ssize_t scull_write (struct file *filp, const char __user *buf, size_t count,loff_t *f_pos);
//子函数:
int scull_trim (struct scull_dev *dev);
struct scull_qset *scull_follow(struct scull_dev *dev, int n);
/***************************************************************************************************
file_operations 函数实现
***************************************************************************************************/
static int scull_open (struct inode *node, struct file *filp)
{
struct scull_dev *dev; //定义scull_dev指针
printk(KERN_WARNING "scull_open() scull opend \n");
dev = container_of(node->i_cdev, struct scull_dev, cdev); //获得scull_dev的地址
filp->private_data = dev; //以后会用到
if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) //为什么要判断? 二次打开?
{
printk(KERN_WARNING " scull_trim()\n");
scull_trim(dev); //释放该设备的所有内存空间
}
return 0;
}
static int scull_release(struct inode *node, struct file *filp)
{
printk(KERN_WARNING "scull_rlease() scull rleased \n");
return 0;
}
//------------------------------read----------------------------------------------------------------
static ssize_t scull_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data; //==scull_devices
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 = 0; //out 处使用的值
printk(KERN_WARNING "scull_read() is called! \n");
//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;
}
//计算索引和偏移量
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;
dptr = scull_follow(dev, item); //查找‘第item个scull_qset的’的位置
if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
{
goto out; //* don't fill holes */
}
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;
}
//-------------------------write--------------------------------------------------------------------
static 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; //==scull_devices
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; //out 处使用的值
printk(KERN_WARNING "scull_write() is writed! \n");
//if (down_interruptible(&dev->sem)) //待解释
//{
// return -ERESTARTSYS;
//}
item = (long)*f_pos / itemsize; //第item个量子集
rest = (long)*f_pos % itemsize; //偏移
s_pos = rest / quantum; //第 s_pos个量子
q_pos = rest % quantum; //偏移q_pos
dptr = scull_follow(dev, item); //查找‘第item个scull_qset的’的位置
if (dptr == NULL)
{
goto out;
}
if (!dptr->data)
{ //分配1000个空间:qest==1000个指针
dptr->data = (void **)kmalloc(qset * sizeof(char *), GFP_KERNEL);
if (!dptr->data)
{
goto out;
}
memset(dptr->data, 0, qset * sizeof(char *));
}
if (dptr->data[s_pos]==NULL)
{
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);//分配4000个空间:quantum==4000个char数据空间
if (!dptr->data[s_pos])
goto out;
}
//写入数据到量子,直到结尾。 为什么不让剩余的数据写到下一个量子?
if (count > quantum - q_pos)
{
count = quantum - q_pos;
}
if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count))//拷贝数据到[quantum==4000]
{
retval = -EFAULT;
goto out;
}
//文件指针文章更改,以便连续写操作
*f_pos += count;
retval = count;
//更新文件大小
if (dev->size < *f_pos)
{
dev->size = *f_pos;
}
out:
//up(&dev->sem);
return retval;
}
/***************************************************************************************************
file_operations 函数实现 需要调用的函数
***************************************************************************************************/
//-------------------------释放该设备的所有内存空间-------------------------------------------------
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next, *dptr;
int qset = dev->qset;
int i;
dptr =dev->data;
//printk(KERN_WARNING " scull_trim() dptr = %d\n",dptr); //应该=0才正确
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;
}
//-------------------------返回第n个scull_qset的地址------------------------------------------------
//若第n个不存在则创建至n个 至少共有n+1个scull_qset
struct scull_qset *scull_follow(struct scull_dev *dev, int n) //n表示链表中有多少个struct scull_qset
{
struct scull_qset *qs = dev->data;
printk(KERN_WARNING " \_scull_follow()\n");
//首次时,建立第一个scull_qset
if (!qs)
{
qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if (qs == NULL)
{
return NULL; /* Never mind */
}
memset(qs, 0, sizeof(struct scull_qset));
}
//遍历整个链表,如果scull_qset为空,进行初始化
while (n--)
{
if (!qs->next) //遍历n-1次,从scull_dev传下来的scull_qset开始
{
qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if (qs->next == NULL)
{
return NULL; /* Never mind */
}
memset(qs->next,0, sizeof(struct scull_qset));
}
qs = qs->next;
continue;
}
return qs;
}
//**************************************************************************************************
//初始化设备的file_operations结构体,必须的。 此结构
struct file_operations scull_fops =
{
.open = scull_open,
.read = scull_read,
.write = scull_write,
.release = scull_release,
};
/***************************************************************************************************
注册和注销模块
***************************************************************************************************/
static int scull_init_module(void)
{
int result;
printk(KERN_WARNING "scull2 initing ... \n");
//------------------注册设备号:scull2----------------------------------------------------------
result = alloc_chrdev_region(&scull_num, scull_minor, 1,"scull2");
if(result < 0)
{
printk(KERN_WARNING "alloc_chrdev_region error \n");
return result;
}
scull_major = MAJOR(scull_num);
printk(KERN_WARNING "scull_num = %d \n",scull_num);
printk(KERN_WARNING "scull_major = %d \n",scull_major);
//分配存储空间给scull_devices, 也给dev分配了空间。
scull_devices = (struct scull_dev*)kmalloc( 1*sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices)
{
printk(KERN_WARNING "kmalloc scull_devices error");
result = -ENOMEM;
goto err1;
}
memset(scull_devices, 0, 1 * sizeof(struct scull_dev)); //初始化空间
//------------------初始化cdev并添加到系统------------------------------------------------------
cdev_init(&scull_devices->cdev, &scull_fops);
scull_devices->cdev.owner = THIS_MODULE;
result = cdev_add(&scull_devices->cdev, scull_num, 1);
if (result)
{
printk(KERN_NOTICE "cdev_add error! \n");
goto err2;
}
//-------------------创建设备节点---------------------------------------------------------------
scull_class = class_create(THIS_MODULE, "scull_class2");//在/sys目录下建立了一个scull_class2的目录
if (IS_ERR(scull_class))
{
printk(KERN_INFO "gvar_class2 create error\n");
goto err3;
//class_destroy(gvar_class);
}
class_device_create(scull_class,NULL,scull_num,NULL,"scull" "%d", MINOR(scull_num));
if(0)
{ printk(KERN_INFO "device_create error\n");
goto err4;
//class_device_destroy()
}
printk(KERN_INFO "create device suess !\n");
return 0;
//------------------失败处理:所有操作将回退----------------------------------------------------
err4:
class_destroy(scull_class);
err3:
cdev_del(&scull_devices->cdev);
err2:
kfree(scull_devices);
err1:
unregister_chrdev_region(scull_num,1);
return result;
}
static void scull_cleanup_module(void)
{
class_device_destroy(scull_class,scull_num);
if(scull_class != NULL)
{
class_device_destroy(scull_class, scull_num);
class_destroy(scull_class);
}
if(&scull_devices->cdev != NULL)
{
cdev_del(&scull_devices->cdev);
}
unregister_chrdev_region(scull_num,1);
printk(KERN_WARNING "scull goodbye! \n");
}
module_init(scull_init_module);
module_exit(scull_cleanup_module);
/***************************************************************************************************
用户使用函数
user2.c
编者:张永辉 2012年10月12日10
***************************************************************************************************/
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
int main(void)
{
int fd, num = 1,ret;
fd = open("/dev/scull0", O_RDWR, S_IRUSR | S_IWUSR);
printf("fd = %d \n",fd);
if (fd != -1 )
{
num = 654;
//ret = write(fd, &num, sizeof(int)); //失败,内核报错
printf("write num = %d\n",num);
//rewinddir(fd);
ret = read(fd, &num, sizeof(int)); //失败,返回0,num的值为改变
printf("read ret = %d\n",ret);
printf("read num = %d\n",num);
close(fd);
}else
{
printf("scull0 Device open failure !\n");
}
return 0;
}
obj-m := scull2.o #模块编译的目标必须以obj-m 这样的形式指出
KERNELDIR ?= /lib/modules/$(shell uname -r)/build #模块的编译必须指定内核源代码的路径
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules #本行开头必须使用 Tab 键
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clearn:
rm -rf *.o *.ko *mod.o [mM]odule* .*.cmd .tmp_versions