正点原子linux驱动笔记-字符设备驱动

1.linux驱动和模块加载函数

Linux驱动有两种运行方式

第一种是将驱动编译Linux内核中,这样当Linux内核启动的时候就会自动运行驱动程序。

第二种是将驱动编译成模块(Linux下模块扩展名为".ko"),在Linux内核启动后使用命令"insmod"或者"modprobe"命令加载驱动模块。在调式驱动的时候一般都选择将其编译为模块,这样我门就不需要重新烧写Linux内核,非常方便。将驱动编译成模块是最方便开发的。

先分析加载的模块的依赖关系,然后都会将所有依赖的模块都加载到内核中

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数
MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息

/****************************************************
ICENSE: 许可证,我们使用GPL协议。GPL协议一般指GNU通用公共许可证。 
GNU通用公共许可证简称为GPL,是由自由软件基金会发行的用于计算机软件
的协议证书,使用该证书的软件被称为自由软件。
*****************************************************/

======================================

2.设备号

在Linux中,为了方便管理,每个设备都有一个设备号,设备号由主设备号和次设备号组成。主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。Linux提供一个dev_t的数据类型表示设备号,在include/linux/types.h里面。

typedef __kernel_dev_t dev_t;
typedef __u32 __kernel_dev_t;
typedef unsigned int __u32;

可以看出dev_t其实就是unsigned int类型,无符号32位 。其中高12位表示主设备号,低20位表示次设备号。所以主设备号范围0~4095。

=======================================

3.注册字符设备函数

我们需要向系统注册一个字符设备,使用函数register_chrdev 。卸载驱动的时候需要注销掉前面注册的字符设备,使用函数unregister_chrdev,注销字符设备。

#include <linux/fs.h>
/* 注册字符设备函数 */
static inline int register_chrdev(unsigned int major, 
              const char *name,const struct file_operations *fops)
{
	return __register_chrdev(major, 0, 256, name, fops);
} 
/* 销毁字符设备函数 */
static inline void unregister_chrdev(unsigned int major, const char *name)
{
	__unregister_chrdev(major, 0, 256, name);
}

/*****************************************************
参数说明:
major :主设备号。Linux下每一个设备都会有设备号,设备号分为主设备号和次设备号。后面有详细的讲。
name :设备的名字。指向遗传字符串。
fops :它时file_operations结构体的指针, file_operations前面我们也说过,就是 Linux 内核驱动操作 
       函数集合。它指向设备的操作函数集合变量。

******************************************************/

设备号的获取

从dev_t获取主设备号和次设备号,MAJOR(dev_t),MINOR(dev_t)。也可以使用主设备号和次设备号构成dev_t,通过MKDEV(major,minor);

file_operations 函数操作集合

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

};

=================================

4.采用更方便方法注册字符设备

静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用

的。而且静态分配设备号很容易带来冲突问题, Linux 社区推荐使用动态分配设备号,在注册字

符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。

卸载驱动的时候释放掉这个设备号。

int register_chrdev_region(dev_t from, 
                           unsigned count, 
                           const char *name)

//@from :要分配的设备编号范围的初始值,明确了主设备号和起始次设备号
//@count:次设备号个数
//@name:相关联的设备名称. (可在/proc/devices目录下查看到), 也即本组设备的驱动名称
//@return: 成功返回0,失败返回负数

int alloc_chrdev_region(dev_t *dev, 
                        unsigned baseminor, 
                        unsigned count, 
                        const char *name)
/*******************************************
dev :保存申请到的设备号。
baseminor :次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这
些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递
增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count :要申请的设备号数量。
name :设备名字。
******************************************/

========================================

5.用户空间和内核空间的数据传递

copy_to_user()函数和copy_from_user()函数

unsigned long copy_to_user(void *to, const void *from, unsigned long n);
这个函数的作用是将内核空间的数据复制到用户空间。其中

to:目标地址(用户空间)
from:源地址(内核空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数

 

unsigned long copy_from_user(void *to, const void *from, unsigned long n);
这个函数的作用是将用户空间的数据复制到内核空间。其中

to:目标地址(内核空间)
from:源地址(用户空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数

==================================

6.代码示例

驱动代码示例:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define CHRDEVBASE_MAJOR      200           //主设备号
#define CHRDEVBASE_NAME     "chrdevbase"    //名字

static char readbuf[100]; /*读缓冲 */
static char writebuf[100];  /* 写缓冲 */
static char kerneldata[] = {"kernel data!"};


static int chrdevbase_open(struct inode *inode, struct file *filp)
{
   // printk("chrdevbase_open\r\n");
    return 0;
}

static int chrdevbase_release(struct inode *inode, struct file *filp)
{
   // printk("chrdevbase_release\r\n");
    return 0;   
}

static ssize_t chrdevbase_read(struct file *filp, __user char *buf, size_t count,
			loff_t *ppos)
{ 
    int ret  = 0;
    //printk("chrdevbase_read\r\n");
    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    ret = copy_to_user(buf, readbuf, count);
    if(ret == 0) {

    } else {


    }


    return 0;  
}

static ssize_t chrdevbase_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
    int ret = 0;
    //printk("chrdevbase_write\r\n");
    ret = copy_from_user(writebuf, buf, count);
    if(ret == 0) {
        printk("kernel recevdata:%s\r\n", writebuf);
    } else {
 
    }


    return 0; 
}

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


static int __init chrdevbase_init(void)
{
    int ret = 0;
    printk("chrdevbase_init\r\n");

    /* 注册字符设备 */
    ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
    if(ret < 0) {
        printk("chrdevbase init failed!\r\n");
    }

	return 0;
}

static void __exit chrdevbase_exit(void)
{
    printk("chrdevbase_exit\r\n");	
    /* 注销字符设备 */
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);

}

/*
 模块入口与出口
 */
module_init(chrdevbase_init);  /* 入口 */
module_exit(chrdevbase_exit);  /* 出口 */

MODULE_LICENSE("GPL");      
MODULE_AUTHOR("zuozhongkai");

================================

应用层代码示例:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>


/*
 *argc:应用程序参数个数
 *argv[]:具体的参数内容,字符串形式 
 *./chrdevbaseAPP  <filename>  <1:2> 1表示读,2表示写
 * ./chrdevbaseAPP /dev/chrdevbase 1    表示从驱动里面读数据
 * ./chrdevbaseAPP /dev/chrdevbase 2    表示向驱动里面写数据
 * */
int main(int argc, char *argv[])
{
    int ret = 0;
    int fd = 0;
    char *filename;
    char readbuf[100], writebuf[100];
    static char usrdata[] = {"usr data!"};

    if(argc != 3) {
        printf("Error usage!\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if(fd < 0 ) {
        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    if(atoi(argv[2]) == 1){ /*  读 */
        /* read */
        ret = read(fd, readbuf, 50);
        if (ret < 0) {
            printf("read file %s failed!\r\n", filename);
        }
        else {
            printf("APP read data:%s\r\n", readbuf);
        }
    }

    /* write */
    if(atoi(argv[2]) == 2) { /* 写 */ 
        memcpy(writebuf, usrdata, sizeof(usrdata));
        ret = write(fd, writebuf, 50);
        if (ret < 0) {
            printf("write file %s failed!\r\n", filename);
        }
        else {

        }
    }

    /* close */
    ret = close(fd);
    if(ret < 0) {
        printf("close file %s falied!\r\n", filename);
    }

    return 0 ;

}
相关推荐
极客代码10 天前
【Linux】设备驱动中的ioctl详解
linux·内核·驱动·设备驱动·iocto
不知火猪1 个月前
最新雷蛇鼠标键盘驱动Razer Synapse 4(雷云) 下载与安装
计算机外设·驱动·雷云·雷蛇驱动
楼兰公子1 个月前
相机主要调试参数
arm开发·驱动·camera·v4l2
极客代码2 个月前
【Linux】【字符设备驱动】深入解析
linux·驱动开发·unix·驱动·字符设备驱动
沐多2 个月前
linux模拟HID USB设备及wireshark USB抓包配置
驱动
极客代码2 个月前
【Linux】内核驱动模块
linux·内核·内核模块·unix·驱动
郁大锤2 个月前
linux alsa-lib snd_pcm_open函数源码分析(四)
linux·音频·pcm·源码分析·驱动·alsa
昵称p2 个月前
如何解决不能将开发板连接到虚拟机的问题(连接显示灰色,不能选中)
驱动·虚拟机无法连接开发板
小仇学长2 个月前
Linux内核编程(十九)SPI子系统一驱动MCP2515(SPI转CAN模块)
linux·驱动·spi·mcp2515
通俗_易懂3 个月前
44-RK3588s调试 camera-engine-rkaiq(rkaiq_3A_server)
人工智能·计算机视觉·rk3588·驱动·camera·imx415