一、程序代码
基于缓冲区的char驱动例子
=============================char_test01.c
该例子支持单缓冲区,支持ioctl
ioctl命令需要写用户态的测试程序buf01.c
=========================================================
/**************************
* char驱动的测试例子1
* 驱动采用内核的缓冲区作为设备,用户态可以用echo或cat读写设备文件,字符串存储在缓冲区中;
* 本驱动支持一个缓冲区硬件
* 用户态可以通过read/write/ioctl来访问设备
* Author: yhd
* Date: 2016-05-11
**************************/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h> //copy_to_user
#include <linux/cdev.h> //cdev,用于注册
#include <linux/proc_fs.h>
//定义硬件需要的信息
//为了保证驱动选择的设备号唯一,驱动应选一个没人用的主设备号,可以让内核帮助选一个,还可以查看文件/proc/devices,找一个没人用的
#define DEF_MAJOR 50
#define DEF_SIZE 100
//定义ioctl命令号(32位)
//#define MEM_RESET 101 //清空缓冲区
//#define MEM_RESIZE 102 //设置缓冲区大小
#define MEM_TYPE 'M'
#define MEM_RESET _IO(MEM_TYPE, 1)
#define MEM_RESIZE _IOW(MEM_TYPE, 2, int)
//如果一个驱动支持多个硬件,每个硬件都会有自己的信息,包括各自的地址,设备号等;
//驱动应该定义一个私有结构体,用于存储每个硬件各自的数据
//如果驱动只支持一个硬件,则可以不必定义
struct mem_priv {
char *start; //缓冲区的基地址
int buf_size; //缓冲区的大小
int wp; //缓冲区的写偏移
dev_t dev_id; //设备号
struct cdev mem_cdev; //用于向VFS注册
};
//定义私有结构体的全局变量
static struct mem_priv dev;
//定义char驱动的各个操作函数
//当用户态open文件时调用
//需要分析一下设备有哪些操作必须在open中完成,如果没有,直接返回0
static int
mem_open(struct inode *inode, struct file *filp)
{
return 0;
}
//当用户态close文件时调用
//完成的工作和open相反
static int
mem_release(struct inode *inode, struct file *filp)
{
return 0;
}
//当用户态read时调用,返回缓冲区的内容。如:
//ret = read(fd, buf, 10);
//内核的read函数增加了一个参数,用于获取或设置文件内部的偏移
static ssize_t
mem_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
size_t cnt = min(count, (size_t)(dev.wp-*f_pos));
if (0==cnt)
return 0;
//copy_to_user(to,from,cnt)
if (copy_to_user(buf,dev.start+*f_pos,cnt))
return -EFAULT;
//更新读指针f_pos
*f_pos += cnt;
return cnt;
}
//当用户态write时调用,将数据写入缓冲区。如:
//ret = write(fd, "hello", 5);
static ssize_t
mem_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
size_t cnt = min(count, (size_t)(dev.buf_size-dev.wp));
if (0 == cnt)
return 0;
//copy_from_user(to,from,cnt)
if (copy_from_user(dev.start+dev.wp,buf,cnt))
return -EFAULT;
//更新wp
dev.wp += cnt;
return cnt;
}
//当用户态用ioctl向设备发命令时调用
//ret = ioctl(fd, 命令号);
//ret = ioctl(fd, 命令号, 命令参数);
static long
mem_ioctl(struct file *filp, unsigned int req, unsigned long arg)
{
char *tmp;
switch (req) {
case MEM_RESET:
memset(dev.start, 0, dev.buf_size);
dev.wp = 0;
break;
case MEM_RESIZE: //释放旧缓冲区,分配新的
tmp = (char *)kzalloc(arg, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
dev.buf_size = arg;
dev.wp = 0;
kfree(dev.start);
dev.start = tmp;
break;
default:
printk("Cannot support ioctl %#x\n", req);
return -1;
}
return 0;
}
//声明并初始化file_operations
static struct file_operations mem_fops = {
.owner = THIS_MODULE,
.open = mem_open,
.release = mem_release,
.read = mem_read,
.write = mem_write,
.unlocked_ioctl = mem_ioctl,
};
static int __init my_init(void)
{
//1.分配并初始化缓冲区
dev.buf_size = DEF_SIZE;
dev.start = (char *)kzalloc(dev.buf_size, GFP_KERNEL);
if (!dev.start)
return -ENOMEM;
dev.wp = 0;
//2.分配设备号(MAJOR+MINOR)
dev.dev_id = MKDEV(DEF_MAJOR, 0);
//3.将选好的设备号和file_ops一起注册到VFS
//将信息封装到cdev结构体中,然后注册
cdev_init(&dev.mem_cdev, &mem_fops);
cdev_add(&dev.mem_cdev, dev.dev_id, 1);
return 0;
}
static void __exit my_exit(void)
{
cdev_del(&dev.mem_cdev);
kfree(dev.start);
}
module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR("YHD");
MODULE_LICENSE("GPL");
===========
makefile
obj-m:=char_test01.o
KERNELDIR:=/lib/modules/3.19.0-25-generic/build
PWD:=$(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
===========
二、测试验证
============================================================
/************************ 用户态的测试例子
* $>./user01 /dev/abc0 reset|resize 1000
* 可以对缓冲区执行reset或resize命令
* Author: zht
* Date: 2016-05-11
***********************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
//命令号应该来自于驱动
#define MEM_TYPE 'M'
#define MEM_RESET _IO(MEM_TYPE, 1)
#define MEM_RESIZE _IOW(MEM_TYPE, 2, int)
int main(void)
{
int fd, value, ret;
char buf[100];
//2.打开设备文件
fd = open("/dev/yhd", O_RDWR);
if (fd<0) {
printf("Open /dev/yhd error\n");
exit(1);
}
ret = ioctl(fd, MEM_RESET);
if(ret < 0 )
{
printf("itcol error\n");
exit(1);
}
ret = write(fd,"hello",5);
if(ret <0)
{
printf("write error\n");
exit(1);
}
//sleep(3);
ret = write(fd,"asdfg",5);
if(ret <0)
{
printf("write error\n");
exit(1);
}
//ret = ioctl(fd, MEM_RESET);
ret = read(fd,buf,15);
if(ret <0)
{
printf("write error\n");
exit(1);
}
printf("buf=%s\n",buf);
close(fd);
return 0;
}
============================================
需要在dev下创建
mknod /dev/yhd c 50 0
生成设备节点,测试两次write 后 ,read buf,看看是否fops×是否可以自动记录
测试结果
buf=helloasdfg