Linux字符设备驱

1、概述

1.1 驱动分类

这里写图片描述

linux下的驱动分为三类:
字符驱动设备:只能一个字节一个字节的读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后顺序进行。字符设备是面向流的设备,常见的字符设备如鼠标、键盘、串口、控制台、LED等。
块设备:是指可以从设备的任意位置读取一定长度的数据设备。块设备如硬盘、磁盘、U盘和SD卡等存储设备。
网络设备:网络设备比较特殊,不在是对文件进行操作,而是由专门的网络接口来实现。应用程序不能直接访问网络设备驱动程序。在/dev目录下也没有文件来表示网络设备。

1.2 主从设备号

主设备号:用于标识驱动程序,主设备号一样的设备文件将使用同一类驱动程序。(1~254)
从设备号:用于标识使用同一驱动程序的不同设备(0~255)

设备文件存放于/dev目录下,使用ls -l可以查看
这里写图片描述
c开头标识字符设备
b开头表示块驱动设备
10表示主设备号,45/50表示从设备号

还可以使用cat /proc/devices查看主设备号的使用情况
这里写图片描述

2、模块的基本框架

//hello.c
#include <linux/module.h>
#include <linux/init.h>
//模块加载函数格式为xxx_init
static int __init hello_init(void){
    printk("hello init\n");
    return 0;
}
//模块卸载函数格式为xxx_exit
static int __exit hello_exit(void) {
    printk("hello exit\n");
    return 0;
}
//声明模块加载函数宏
module_init(hello_init);
//声明模块卸载函数宏
module_exit(hello_exit);
//模块许可证明,描述内核模块的许可权限
MODULE_LICENSE("GPL");

驱动的编译有两种方式:
放入Linux内核源码中编译
采用独立的方法编译模块

独立编译需要编写makefile文件

ifneq ($(KERNELRELEASE),)
    obj-m := hello.o    #模块名字,与C文件同名
else
    KERNELDIR ?= /home/ms/FirePrime/Source/Android5.1/kernel #内核路径
    PWD := $(shell pwd) #当前路径
default:    #编译过程
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules #-C 找到内核的makefile
clean:
    rm -rf *.o *~ .depend .*.cmd *.ko .tmp_versions
endif
2.2 内核模块命令

lsmod 列举当前系统中的所有模块
insmod xxx.ko 加载指定模块到内核
rmmod xxx 卸载指定模块(不需要.ko后缀)

3、字符驱动框架

字符设备驱动靠file_operations连接

struct file_operations {
        struct module *owner;    //防止模块还在被使用的时候被卸载
        //方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
        loff_t        (*llseek) ();
        ssize_t       (*read) ();
        ssize_t       (*write) ();
        unsigned int (*poll) ();//这是一个设备驱动中的轮询函数
        int           (*ioctl) ();//系统调用提供了发出设备特定命令的方法
        int           (*mmap) ();//用来请求将设备内存映射到进程的地址空间
        int           (*open) ();
        int           (*flush) ();
        int           (*release) ();//在文件结构被释放时引用这个操作.
};
3.2 字符设备驱动的小练习:

具体步骤:

module_init(drvdemo_init);
//注册file_operations
register_chrdev(0, "demo_chrdev", &fops);
//创建设备类
demo_class = class_create(THIS_MODULE, "demo_class");
//创建设备节点
device_create(demo_class,NULL, MKDEV(major, i), NULL,"demo%d",i);

//驱动完成 可以使用

module_exit(drvdemo_exit);
//字符设备注销函数
unregister_chrdev(major, "demo_chrdev");
//删除设备节点
device_destroy(demo_class, MKDEV(major, i));
//销毁创建好的设备类
class_destroy(demo_class);
#include <linux/init.h> 
#include <linux/module.h>
#include <linux/kernel.h>
/*结构体file_operations定义的头文件*/
#include <linux/fs.h>
/*声明copy_to/from_user函数的头文件*/
#include <linux/uaccess.h>
/*声明class_create 和device_create相关信息*/
#include <linux/device.h>

#define DEVICE_COUNT            2

/*记录当前驱动所占用的主设备号*/
static int major = 0;

static int demo_open (struct inode *pnode, struct file *filp)
{
    printk("==> %s  major: %d  minor: %d\n",
                    __FUNCTION__, imajor(pnode), iminor(pnode));
    return 0;
}

static ssize_t demo_read (struct file *filp, char __user *buf, size_t count, loff_t *offp)
{
    unsigned char ary[100] = "read successfully\n";
    unsigned long len = min(count, sizeof(ary));    //min是个宏,用来获取两个数中较小的值
    int retval;

    printk("==>%s  major: %d  minor: %d\n",
                    __FUNCTION__, imajor(filp->f_dentry->d_inode), 
                    iminor(filp->f_dentry->d_inode));

    if(copy_to_user(buf, ary, len) != 0){
        retval = -EFAULT;
        goto cp_err;
    }

    return len; //成功返回实际传输的字节数
cp_err:
    return retval;  
}

static ssize_t demo_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp)
{
    unsigned char ary[100] = "";
    unsigned long len = min(count, sizeof(ary));    //min是个宏,用来获取两个数中较小的值
    int retval;

    printk("==>%s  major: %d  minor: %d\n",
                    __FUNCTION__, imajor(filp->f_dentry->d_inode), 
                    iminor(filp->f_dentry->d_inode));

    if(copy_from_user(ary, buf, len) != 0){
        retval = -EFAULT;
        goto cp_err;
    }

    printk("writing context==> %s\n",ary);

    return len; //成功返回实际传输的字节数
cp_err:
    return retval;  
}

static int demo_release (struct inode *pnode, struct file *filp)
{
    printk("==>%s  major: %d  minor: %d\n",
                    __FUNCTION__, imajor(pnode), iminor(pnode));
    return 0;
}

/*定义file_operations结构体变量*/
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = demo_read,
    .write = demo_write,
    .open = demo_open,
    .release = demo_release,
};  

static struct class *demo_class;
static int __init drvdemo_init(void)
{
    struct device *demo_device;
    int i;
    int retval;

    printk("==>drvdemo_init\n");

    //注册字符驱动函数
    //成功返回动态分配好的主设备号,失败返回错误码(负值)
    //register_chrdev(主设备号 0为自动分配,设备名称 在/proc/devices/下,file_operations)
    major = register_chrdev(0, "demo_chrdev", &fops);
    if(major < 0){
        retval = major;
        goto chrdev_err;
    }

    /*创建设备类*/
    demo_class = class_create(THIS_MODULE, "demo_class");
    if(IS_ERR(demo_class)){
        retval =  PTR_ERR(demo_class);
        goto class_err; 
    }

    for(i=0; i<DEVICE_COUNT; i++){  //最多可创建255个设备节点(register_chrdev函数会申请0-254范围的从设备号)
        //根据类创建设备节点,通知用户在“/dev/”目录下创件名字为demoX的设备文件
        demo_device = device_create(demo_class,NULL, MKDEV(major, i), NULL,"demo%d",i);
        if(IS_ERR(demo_device)){
            retval = PTR_ERR(demo_device);
            goto device_err;
        }
    }
    return 0;

device_err:
    while(i--)  //设备节点创建的回滚操作
        device_destroy(demo_class,MKDEV(major, i));
    class_destroy(demo_class);  //删除设备类
class_err:
    unregister_chrdev(major, "demo_chrdev");
chrdev_err:
    return retval;
}

static void __exit drvdemo_exit(void)
{
    int i;

    printk("==>drvdemo_exit\n");
    /*注销字符驱动函数,无返回值,major为已分配的主设备号*/
    unregister_chrdev(major, "demo_chrdev");
    /*删除设备节点和设备类*/
    for(i=0; i<DEVICE_COUNT; i++)
        device_destroy(demo_class, MKDEV(major, i));
    class_destroy(demo_class);
}

module_init(drvdemo_init);
module_exit(drvdemo_exit);


MODULE_LICENSE("GPL");

Makefile:

#如果已定义KERNELRELEASE,说明是由内核构造系统调用的
#可以利用内建语句
ifneq ($(KERNELRELEASE),) 
    obj-m +=demo_chrdev.o
    #此时由内核构造系统调用
else 
    #定义并记录内核源码路径
    KERNELDIR = /home/ms/FirePrime/Source/Android5.1/kernel
    #记录当前工程目录
    PWD := $(shell pwd)

default: 
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 
    @rm -rf *.o .t* .m* .*.cmd *.mod.c *.order *.symvers

endif

clean:
    rm -rf *.ko *.o .t* .m* .*.cmd *.mod.c *.order *.symvers
3.3 struct inode结构体记录了文件系统中文件的信息,如文件大小、创建者等。每个文件都有与之对应的inode节点。

inode包含了主从设备号,可以通过宏获取

unsigned int imajor(struct inode *inode);//主设备
unsigned int iminor(struct inode *inode);//从设备
dev_t MKDEV(int imajor, int iminor);//将主从设备号合成dev_t结构体
3.4 内核空间与用户空间的数据拷贝:
//从内核空间拷贝n字节数据到用户空间
copy_to_user(user_buffer, kernel_buffer, n)
//从用户空间拷贝n字节数据到内核空间
copy_from_user(kernel_buffer, user_buffer, n)
//从内核空间拷贝一数据(任意类型)变量到用户空间
put_user(kernel_value, user_buffer)
//从用户空间拷贝一数据(任意类型)变量到内核空间
get_user(kernel_value, user_buffer)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值