Linux笔记 - 嵌入式Linux-字符设备驱动开发初探

一、字符设备驱动框架

        1、Linux之下一切皆文件,驱动设备的表现形式也是文件,具体存放在/dev/下,例如某lcd驱动设备文件=> /dev/lcd.

        2、驱动根据不同的驱动框架有不一样的编写方式,需要根据驱动框架来编写驱动程序,比如在编写字符设备驱动的时候,就需要重点编写出应用程序相对应的打开、关闭、读、写等函数。编写的时候还要考虑应用程序的开发便利性。

        3、字符设备驱动的编写主要就是驱动对应的open、close、read等函数,具体是实现file_operstions这个结构体里面是成员变量的实现。

二、驱动模块的加载与卸载

        1、设备驱动程序既可以编译到内核中,也可以编译成模块。前者的优势是编译进内核后每次系统启动就会运行改驱动程序,但缺点是遇到需要修改驱动的情况就得编译整个内核。后者的优势是我们需要用到哪个模块就编译哪个模块驱动程序,便于开发。但是没有哪种最好,还是看应用场景。

        2、编写驱动时候的注意事项:

            1) 编译驱动需要用到内核源码,将源码解压缩并编译,从而得到zImage和.dtb文件,因为需要用到这两个文件来启动系统。

            2) 驱动程序编译生成的.ko文件要放到根文件系统中。加载驱动命令有insmod与modprobe,移除则使用命令rmmod来卸载。如果使用modprobe命令来加载一个新模块要先调用depmod命令。驱动模块加载成功后可以使用命令lsmod来查看。

            3)  在编写驱动程序如果需要输出信息要用printk函数,printf是运行在用户态的,printk是运行在内核态。printk可以根据日志级别对输出信息进行分类,一共有8个等级,第0级是最高优先级,一般是紧急情况如内核崩溃,第7级是最低优先级,如调试信息。输出格式如下:

printk(KERN_EMERG "gsmi: Log Shutdown Reason\n");

三、驱动模块的注册与注销

        1、当需要向系统注册一个字符设备可使用函数register_chrdev,而当我们需要卸载驱动的时候就需要注销掉先前注册过的字符设备,这时候采用函数unregister_chrdev注销字符设备。

        2、register_chrdev会将主设备号下的所有次设备号都使用了,不够智能。

        3、注册注销函数返回值是个负数就是出错了,linux内核函数大部分如此。

四、设备号

        1、Linux内核使用32位的数据类型dev_t来表示,其中高12位表示主设备号,低20位表示次设备号,所以主设备号的范围在0~4095之间。每一个设备的设备号是唯一的!

        2、从dev_t获取主设备号和次设备号,可以通过MAJOR(dev_t)函数获取主设备号,MINOR(dev_t)函数获取次设备号。也可以通过函数MKDEV(major,minor)同时构成dev_t从而获取设备号整体信息。

五、file_operations的实现

        1、需要哪些函数就实现哪些函数不需要全部实现。

        2、实现如open,close,read,write等基础函数可能需要的头文件有:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <uapi/linux/types.h>
#include <linux/fs.h>

      3、结构组织

struct file_operations {

    struct module *owner;

    loff_t (*llseek) (struct file *, loff_t, int);

    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);

    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);

    int (*iterate) (struct file *, struct dir_context *);

    unsigned int (*poll) (struct file *, struct poll_table_struct *);

    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

    int (*mmap) (struct file *, struct vm_area_struct *);

    int (*mremap)(struct file *, struct vm_area_struct *);

    int (*open) (struct inode *, struct file *);

    int (*flush) (struct file *, fl_owner_t id);

    int (*release) (struct inode *, struct file *);

    int (*fsync) (struct file *, loff_t, loff_t, int datasync);

    int (*aio_fsync) (struct kiocb *, int datasync);

    int (*fasync) (int, struct file *, int);

    int (*lock) (struct file *, int, struct file_lock *);

    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

    int (*check_flags)(int);

    int (*flock) (struct file *, int, struct file_lock *);

    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

    int (*setlease)(struct file *, long, struct file_lock **, void **);

    long (*fallocate)(struct file *file, int mode, loff_t offset,

              loff_t len);

    void (*show_fdinfo)(struct seq_file *m, struct file *f);

#ifndef CONFIG_MMU

    unsigned (*mmap_capabilities)(struct file *);

#endif

};

六、字符设备驱动框架的构建

        1、

七、应用程序编写

        1、linux之下一切皆文件,涉及文件就有open,read,write等函数,编写应用程序需要使用到这些函数。可以通过在ubuntu中输入man + 函数名 查看函数使用方法。

八、应用程序测试

        1、加载驱动

modprobe chrdevbase.ko

        2、查看加载情况

/lib/modules/4.1.15 # lsmod
Module                  Size  Used by    Tainted: G  
chrdevbase               695  0 

        3、创建设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。“c”表示这是个字符设备,“ 200”是设备的主设备号,“ 0”是设备的次设备号

/dev # mknod /dev/chrdevbase c 200 0
/dev # ls chrdev*
chrdevbase
/ # ls /dev/chrdevbase  -l
crw-r--r--    1 0        0         200,   0 Jan  1 05:04 /dev/chrdevbase

        4、运行测试

/lib/modules/4.1.15 # ./chrdevbaseAPP /dev/chrdevbase 
chrdevbase_open
chrdevbase_read
chrdevbase_write
chrdevbase_release

        对应驱动程序内字符设备我们实现的函数集。

/*
* 字符设备的操作集合
*/
static struct file_operations chrdevbase_fops={
    .owner = THIS_MODULE,
    .open = chrdevbase_open,
    .release = chrdevbase_release,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
};

九、完善虚拟设备驱动

        1、完善驱动使应用程序能够对驱动读写操作。如应用程序读取驱动程序内的字符串,或者应用程序驱动程序写字符串。

        2、驱动给应用程序传递数据要用函数cope_to_user,驱动程序接收应用程序传递来的数据要用函数copy_from_user。

        3、通过这两个函数我们可以实现应用程序和驱动程序的交互,比如我们在应用程序调用read函数:

 ret = read(fd, read_buf, 50);

        就会跳转到驱动程序里面我们编写的chrdevbase_read函数:

static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)

        然后我们在函数内通过函数cope_to_user将我们需要发送的数据发送给应用程序,将read_buf通过buf发送出去,一共cnt字节

    memcpy(read_buff, kernel_data, sizeof(kernel_data));
    ret = copy_to_user(buf, read_buff, cnt);

        应用程序收到驱动程序发来的数据就会存放到read_buf中:

       ret = read(fd, read_buf, 50);
        if (ret < 0)
        {
            printf("read file %s failed\r\n, filename");
        }
        else
        {
            printf("APP read data:%s\r\n", read_buf);
        }

        写数据原理也类似,但是是通过函数copy_from_user来实现。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值