分享一个char驱动的实例

一、程序代码

基于缓冲区的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


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值