在上一节 中讨论了字符设备的基本模型,本节在上一节的基础上继续完善,本节将增加file_operations
中的read
、write
和llseek
三个方法。
read和write方法完成的任务是相似的,即,拷贝数据到应用程序空间,或反过来从应用程序空间拷贝数据到内核空间,它们的原型也很相似,如下:
ssize_t read(struct file *filp, char __user *buffer, size_t count, loff_t *ppos);
ssize_t write(struct file *file, const char __user *buffer, size_t count, loff_t* ppos);
对于这两个方法的参数解读如下:
copy_from_user
和copy_to_user
两个函数来操作,其原型如下:unsigned long copy_to_user(void __user* to, const void* from, unsigned long count);
unsigned long copy_from_user(void* to, const void __user* from, unsigned long count);
这两个函数在拷贝是会对地址做检查,返回实际拷贝的字节数。
read
和write
都要在偏移*ppos
的基础上对文件进行操作。需要注意的是在读取或者写入文件内容后需要更新*ppos
,之后内核会将文件位置的改变传播回file
结构。llseek的作用就是重新定位文件访问(read
或者write
)的偏移,函数原型为:
loff_t char_driver_llseek(struct file *filp, loff_t offset, int whence);
至此,本节的内容差不多了,下面给出完整的代码:
char_driver.c
#include
#include
#include
#include
#include
#include
#include
#define MEM_SIZE 4096
struct mem_dev
{
char *buf;
unsigned long buf_size;
//struct cdev cdev;
};
struct mem_dev *mem_devp;
static int char_driver_open(struct inode *inode, struct file *filp)
{
filp->private_data = mem_devp; // 将全局变量mem_devp赋给filp->private_data,减少对全局变量的依赖
printk("<0> open dev:%d\n", iminor(inode));
return 0;
}
static ssize_t char_driver_read(struct file *filp, char __user *buffer, size_t count, loff_t *ppos)
{
int read_size;
struct mem_dev *my_dev;
my_dev = filp->private_data;
if (count > my_dev->buf_size - *ppos) {
read_size = my_dev->buf_size - *ppos;
} else {
read_size = count;
}
copy_to_user(buffer, my_dev->buf + *ppos, read_size);
*ppos += read_size;
//printk("<0> char_read to user:%s\n", buffer);
return read_size;
}
static ssize_t char_driver_write(struct file *filp, const char __user *buffer, size_t count, loff_t* ppos)
{
struct mem_dev *my_dev;
int write_size;
my_dev = filp->private_data;
if (count > my_dev->buf_size - *ppos) {
write_size = my_dev->buf_size - *ppos;
} else {
write_size = count;
}
copy_from_user(my_dev->buf + filp->f_pos, buffer, write_size);
*ppos += count;
//printk("<0> char_write from user:%s ppos:%d\n", (struct mem_dev*)(file->private_data)->buf, (int)*ppos);
return write_size;
}
static loff_t char_driver_llseek(struct file *filp, loff_t offset, int whence)
{
loff_t newpos;
struct mem_dev *my_dev = filp->private_data;
switch(whence) {
case 0: /* SEEK_SET */
newpos = offset;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + offset;
break;
case 2: /* SEEK_END */
newpos = my_dev->buf_size -1 + offset;
break;
default: /* can't happen */
return -EINVAL;
}
if ((newpos < 0) || (newpos > MEM_SIZE)) {
return -EINVAL;
}
filp->f_pos = newpos;
return newpos;
}
static int char_driver_release(struct inode *inode, struct file *filp)
{
printk(KERN_EMERG"close dev:%d\n", MINOR(inode->i_rdev));
return 0;
}
static struct file_operations char_driver_fops = {
.owner = THIS_MODULE,
.open = char_driver_open,
.write = char_driver_write,
.read = char_driver_read,
.llseek = char_driver_llseek,
.release = char_driver_release,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "char_driver",
.fops = &char_driver_fops,
};
static struct mem_dev* alloc_mem_dev(void)
{
struct mem_dev* devp = kmalloc(sizeof(struct mem_dev), GFP_KERNEL);
if (!devp) { /*申请失败*/
return NULL;
}
memset(devp, 0, sizeof(struct mem_dev));
devp->buf = kmalloc(MEM_SIZE, GFP_KERNEL);
if (NULL == devp->buf) {
kfree(devp);
return NULL;
}
devp->buf_size = MEM_SIZE;
return devp;
}
static void release_mem_dev(struct mem_dev** devp)
{
if (NULL == devp) {
return;
}
kfree((*devp)->buf);
(*devp)->buf = NULL;
kfree(*devp);
*devp = NULL;
}
static int __init char_driver_init(void)
{
int ret;
mem_devp = alloc_mem_dev();
if (!mem_devp) /*申请失败*/
{
printk(KERN_EMERG"alloc mem_dev error!\n");
return - ENOMEM;
}
ret = misc_register(&misc);
printk(KERN_EMERG"hello driver dev_init!\n");
return ret;
}
static void __exit char_driver_exit(void)
{
release_mem_dev(&mem_devp);
misc_deregister(&misc);
printk(KERN_EMERG"hello driver dev_exit!\n");
}
MODULE_LICENSE("GPL");
module_init(char_driver_init);
module_exit(char_driver_exit);
app.c
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
char buf[128] = {0};
fd = open("/dev/char_driver", O_RDWR);
if(0 > fd){
perror("can not open 6410_led:");
}
write(fd, "hello driver!", 13);
lseek(fd, SEEK_SET, 0);
read(fd, buf, 5);
printf("%s\n", buf);
memset(buf, 0, sizeof(buf));
read(fd, buf, 10);
printf("%s\n", buf);
close(fd);
return 0;
}
Makefile和上一节的一样,本节不再列出。
编译加载模块的过程不再列出来了,分别为一下步骤:
#make
#insmod char_driver.ko
测试驱动功能
由于本驱动模拟的是一个文件的读写,那么接下来看看是否能实现功能:
从上图结果可知,首先我写入了“hello driver!”13个字符,分两次读取,读取结果和预期相同,至于第二次读取的内容打印出来后面有乱码,是由于我在驱动中没有记录实际写入的字节数,如果有兴趣的话可以自己尝试实现。