[读书笔记]字符设备驱动程序(第三章)

一、综述

本章开始,我们就要学会写一个最基本的字符设备驱动了,麻雀虽小五脏俱全。这是本章的重点,首先要弄明白一些基本的数据结构等相关知识,在去尝试编写驱动。

知识点

1.主设备号和次设备号
a.
一个主设备 对应 一个驱动程序
b.
次设备号 确定 具体的设备
c.
dev_t(在linux/types.h中)保存设备编号,
前12位 - 主设备号
后20位 - 次设备号
MAJOR(dev_t dev):获取主设备号
MANOR(dev_t dev) : 获得次设备号
MKDEV(int major,int minor) : 主设备号和次设备号转换成dev_t
d.
分配和释放设备编号
静态分配: int register_chrdev_region(dev_t first,unsigned int count ,char *name);
first:起始编号,通常为0
count: 连续请求的设备编号个数
name:设备名称,将会出现在 /proc/devices和sysfs中
成功:返回0
失败:返回负数

动态分配
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name)
dev:仅用于输出的参数,在成功完成调用后将班车已分配范围的第一个编号
firstminor:第一个次设备号,通常为0
count和那么跟上面的一样
e.释放函数
释放函数:void unregister_chrdev_region(dev_t first,unsigned int count)

2.字符设备的注册
方式一:注册独立的cdev结构
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_ops;
方式2:将cdev内嵌到自己的设备结构中
调用以下函数来初始化
void cdev_init(strcuct cdev *cdev,struct file_operations *fops);
指定cdev的所有者和操作函数
cdev.ower = THIS_MODULE;
cdev.ops = &my_ops;
把字符设备添加到内核
int cdev_add(struct cdev *dev,dev_t num,unsigned int count);
在适当的地方删除
void cdev_del(struct cdev *dev);
3.三个重要的数据结构
struct file_operations:保存操作字符驱动程序的方法
strcut file:表示打开一个文件
strucr inode:表示一个磁盘上的文件
大多数设备驱动程序都会用到这三个数据结构
其他知识点
#include <linux/kernel.h>
container_of(pointer,type,field)
一个方便使用的宏,可用于从包含在某个数据结构中的指针获得结构体本身的指针。
#include <linux/uaccess.h>
该头文件声明了内核代码和用户空间之间移动数据的函数
unsigned long copy(void *to,const void *from,unsigned long count);
unsigned long copy(void *to,const void *from,unsigned long count);
用户空间和内核空间拷贝数据

实践环节--在安卓系统中编写字符驱动

编写一个名字为hello的字符驱动设备(参照书本和罗升阳博客),自己跟着一遍遍敲打出来,还是会遇到很多编译错误,也学习到很多东西,自己动手,丰衣足食,理解也更深!
学习的过程中,大家别跟着代码从头敲到尾,要按照思路来写,比如我先定义一些需要的变量和方法,然后先写一些空方法,把框架搭好,module_init(),module_exit()等相关方法
再去传统操作设备的方法,接着再去写devfs文件系统的方法
有思路的去写!!!
有些东西已经抛弃了
init_MUTEX(),在内核2.6版本就抛弃了,取而代之的是sema_init();

具体实现

kernel-3.18/drivers/misc/mediatek/创建hello目录
kernel-3.18/drivers/misc/mediatek/hello/
--hello.h
--hello.c
--Kconfig
--Makefile

hello.h

#include <linux/cdev.h> 
#include <linux/semaphore.h>
//节点名称
#define HELLO_DEVICE_NODE_NAME "hello"

//定义hello_dev结构体
struct hello_dev {
    int val;//变量--模拟寄存器
    struct semaphore sem;//信号量 互斥访问 防止竞态
    struct cdev dev;//字符设备
};

hello.c

引入头文件,定义相关的方法

#include <linux/init.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/semaphore.h>
#include <linux/kdev_t.h>
#include <linux/device.h>

#include "hello.h"

/*主设备号 和 从设备号 变量*/
static int hello_major = 0;
static int hello_minor = 0;

/*设备类别和设备变量*/
static struct class *hello_class = NULL;
static struct hello_dev* hello_dev = NULL;

/*传统的设备文件操作方法*/
static int hello_open(struct inode *inode,struct file *filp);
static int hello_release(struct inode *inode,struct file *filp);
static ssize_t hello_read(struct file *filp,char __user *buf,
size_t count,loff_t *f_ops);
static ssize_t hello_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_ops);

/*设备文件操作方法表*/
static struct file_operations hello_fops = {
    .owner = THIS_MODULE,
    .open = hello_open,
    .release = hello_release,
    .write = hello_write,
    
};
/*访问设置属性方法*/
static ssize_t hello_val_show(struct device *dev,struct device_attribute *attr,char *buf);
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

/*定义设备属性*/
static DEVICE_ATTR(val,S_IRUGO | S_IWUSR,hello_val_show,hello_val_store);

传统的设备文件操作方法
定义传统的设备文件访问方法,主要是定义hello_open、hello_release、hello_read和hello_write这四个打开、释放、读和写设备文件的方法:

/*打开设备方法*/
static int hello_open(struct inode *inode,struct file *filp) {
    struct hello_dev *dev;
    dev = container_of(inode->i_cdev,struct hello_dev,dev);
    filp->private_data = dev;

    return 0;
}
/*是否设备方法 这里空实现*/
static int hello_release(struct inode *inode,struct file * filp) {

    return 0;

}
/*读取寄存器设备 val的值*/
static ssize_t hello_read(struct file *filp,char __user *buf,
size_t count,loff_t *f_ops) {
    ssize_t err = 0;
    struct hello_dev *dev = filp->private_data;

    /*同步访问*/
    if(down_interruptible(&(dev->sem)));
        return -ERESTARTSYS;
    
    if(count < sizeof(dev->val)){
        goto out;
    }
    /*将寄存器val的值拷贝到用户提供的缓存区*/
    if(copy_to_user(buf,&(dev->val),sizeof(dev->val))){
        err = -EFAULT;
        goto out;
    }

    out:
    up(&(dev->sem));
    return err;
}

/*写设备寄存器val的值*/
    return 0;

}
/*读取寄存器设备 val的值*/
static ssize_t hello_read(struct file *filp,char __user *buf,
size_t count,loff_t *f_ops) {
    ssize_t err = 0;
    struct hello_dev *dev = filp->private_data;

    /*同步访问*/
    if(down_interruptible(&(dev->sem)));
        return -ERESTARTSYS;
    
    if(count < sizeof(dev->val)){
        goto out;
    }
    /*将寄存器val的值拷贝到用户提供的缓存区*/
    if(copy_to_user(buf,&(dev->val),sizeof(dev->val))){
        err = -EFAULT;
        goto out;
    }

    out:
    up(&(dev->sem));
    return err;
}

/*写设备寄存器val的值*/
static ssize_t hello_write(struct file *filp,const char __user *buf,
size_t count,loff_t * f_ops){
    ssize_t err = 0;
    struct hello_dev *dev = filp->private_data;

    /*同步访问*/
    if(down_interruptible(&(dev->sem))){
        return -ERESTARTSYS;
    }

    if(count != sizeof(dev->val)) {
        goto out;
    }
    /*将用户提供的缓存区的值写到设备寄存器中去*/
    if(copy_from_user(&(dev->val),buf,count)){
        err = EFAULT;
        goto out;
    }
    
    err = sizeof(dev->val);//成功写入的数据大小

out:
    up(&(dev->sem));
    return err;

}
/*
字符设备注册和初始化的方式
方式2:将cdev内嵌到自己的设备结构中
调用以下函数来初始化
void cdev_init(strcuct cdev *cdev,struct file_operations *fops);
指定cdev的所有者和操作函数
cdev.ower = THIS_MODULE;
cdev.ops = &my_ops;
把字符设备添加到内核
} 
/*写设备寄存器val的值*/
static ssize_t hello_write(struct file *filp,const char __user *buf,
size_t count,loff_t * f_ops){
    ssize_t err = 0;
    struct hello_dev *dev = filp->private_data;

    /*同步访问*/
    if(down_interruptible(&(dev->sem))){
        return -ERESTARTSYS;
    }

    if(count != sizeof(dev->val)) {
        goto out;
    }
    /*将用户提供的缓存区的值写到设备寄存器中去*/
    if(copy_from_user(&(dev->val),buf,count)){
        err = EFAULT;
        goto out;
    }
    
    err = sizeof(dev->val);//成功写入的数据大小

out:
    up(&(dev->sem));
    return err;

}

devfs文件系统访问方法
定义通过devfs文件系统访问方法,这里把设备的寄存器val看成是设备的一个属性,通过读写这个属性来对设备进行访问,主要是实现hello_val_show和hello_val_store两个方法,同时定义了两个内部使用的访问val值的方法__hello_get_val和__hello_set_val

//---------------------------方式2----------------------------
/*读取寄存器val的值到缓存区buf中 内部使用*/
static ssize_t __hello_get_val(struct hello_dev *dev,char *buf) {
    int val = 0;

    /*同步访问*/
    if(down_interruptible(&(dev->sem))){
        return -ERESTARTSYS;
    }

    val = dev->val;
    up(&(dev->sem));

    return snprintf(buf,PAGE_SIZE,"%d\n",val);
}
/*把缓冲区buf的值写到设备寄存器val中去 内部使用*/
static ssize_t __hello_set_val(struct hello_dev *dev,const char *buf,size_t count){
    int val = 0;
    //把字符串转换成数字
    val = simple_strtol(buf,NULL,10);
    //同步访问
    if(down_interruptible(&(dev->sem))){
        return -ERESTARTSYS;
    }
    
    dev->val = val;
    up(&(dev->sem));

    return count;

}

/*读取设备属性 val*/
static ssize_t hello_val_show(struct device *dev,struct device_attribute *attr,char *buf){
    struct hello_dev *hdev = (struct hello_dev*)dev_get_drvdata(dev);
    
    return __hello_get_val(hdev,buf);
}
/*写设备属性val*/
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count){
    struct hello_dev *hdev = (struct hello_dev*)dev_get_drvdata(dev);
    
    return __hello_set_val(hdev,buf,count);
}

定义模块加载和卸载方法,这里只要是执行设备注册和初始化操作

/*初始化设备*/
static int hello_setup_dev(struct hello_dev *dev){
    int err;
    dev_t devno = MKDEV(hello_major,hello_minor);

    //先清空dev,书上没有调用
    memset(dev,0,sizeof(struct hello_dev));
    //调用cdev_init初始化,接着绑定操作设备的方法
    cdev_init(&(dev->dev),&hello_fops);
    dev->dev.owner = THIS_MODULE;
    dev->dev.ops = &hello_fops;
    //注册字符设备
    err = cdev_add(&(dev->dev),devno,1);
    if(err)
        return err;
    //初始化信号量和寄存器的值
    sema_init(&(dev->sem),1);
    dev->val = 0;
    
    return 0;
}

/*模块加载方法*/
static int hello_init(void){
    int err = -1;
    dev_t dev = 0;
    struct device* temp = NULL;

    printk("[%s] is init\n",__func__);
    
    //动态分配主设备号和从设备好
    err = alloc_chrdev_region(&dev,0,1,HELLO_DEVICE_NODE_NAME);
    if(err < 0){
        printk("faile to alloc_chrdev_region()\n");
        goto fail;
    }
    hello_major = MAJOR(dev);
    hello_minor = MINOR(dev);

    //分配内存
    hello_dev = kmalloc(sizeof(struct hello_dev),GFP_KERNEL);
    if(!hello_dev){
        err = -ENOMEM;
        printk("faile to alooc hello_dev\n");
        goto unregister;
    }
    
    //初始化设备
    err = hello_setup_dev(hello_dev);
    if(err){
        printk("failed to set up hello device\n");
        goto cleanup;
    }

    //在 /sys/class目录下创建设备类别目录hello
    hello_class = class_create(THIS_MODULE,"hello");
    if(IS_ERR(hello_class)){
        err = -1;
        printk(KERN_ALERT"Failed to create hello class.\n");
        goto destroy_cdev;
    }
    //在dev目录 和 sys/class/hello目录下分别创建设备文件hello
    temp = device_create(hello_class,NULL,dev,NULL,"hello");
    if(IS_ERR(temp)){
        err = -2;
        printk(KERN_ALERT"Failed to create hello device.");
        goto destroy_class;   
    }

    //在 sys/class/hello/hello目录下创建属性文件val
    
    err = device_create_file(temp,&dev_attr_val);
    if(err < 0){
        printk(KERN_ALERT"Failed to create attribute val.");
        goto destroy_device;
    }
    
    dev_set_drvdata(temp,hello_dev);

    printk("success to init hello device\n");
    return 0;

destroy_device:
    device_destroy(hello_class, dev); 
destroy_class:
    class_destroy(hello_class);
destroy_cdev:
    cdev_del(&(hello_dev->dev));
cleanup:
    kfree(hello_dev);
unregister:
    unregister_chrdev_region(MKDEV(hello_major,hello_minor),1);
fail:
    return err;
     
}

/*模块卸载方法*/
static void hello_exit(void){
    dev_t devno = MKDEV(hello_major,hello_minor);

    printk("destroy hello device \n");
    
    //销毁设备类别和设备
    if(hello_class){
        device_destroy(hello_class,MKDEV(hello_major,hello_minor));
        class_destroy(hello_class);
    }

    //删除字符设备和释放设备内存
    if(hello_dev){
        cdev_del(&(hello_dev->dev));
        kfree(hello_dev);
    }

    //释放设备号
    unregister_chrdev_region(devno,1);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("First Andorid Driver");

module_init(hello_init);
module_exit(hello_exit);

Kconfig

config HELLO        
    tristate "First Android Driver"
    default n
    help
    This is my first android driver.

Makefile

obj-y += hello.o

接着
kernel-3.18/drivers/misc/mediatek/Kconfig添加一句
source "drivers/misc/mediatek/hello/Kconfig"
kernel-3.18/drivers/misc/mediatek/Makefile添加一句
obj-y += hello/

最后 使用编译命令 ./mk bootimage
把生成的bootimage刷机即可


out

结果

进入到dev目录,可以看到hello设备文件:


adb

进入到sys/class目录,可以看到hello目录,hello文件,
进入到下一层hello目录,可以看到val文件:


adb

重启问题

log
static int hello_init(void){
    int err = -1; 
    dev_t dev = 0;
    
    printk("[%s] is init\n",__func__);
    
    //动态分配主设备号和从设备好
    err = alloc_chrdev_region(dev,0,1,HELLO_DEVICE_NODE_NAME); 
//省略代码
}

问题出在alloc_chrdev_region()这里,原来发现第一个参数dev需要的是地址,导致了重启
修改:alloc_chrdev_region(&dev,0,1,HELLO_DEVICE_NODE_NAME);

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容