正点原子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 ;

}
相关推荐
郁大锤3 天前
linux alsa-lib snd_pcm_open函数源码分析(四)
linux·音频·pcm·源码分析·驱动·alsa
昵称p9 天前
如何解决不能将开发板连接到虚拟机的问题(连接显示灰色,不能选中)
驱动·虚拟机无法连接开发板
小仇学长13 天前
Linux内核编程(十九)SPI子系统一驱动MCP2515(SPI转CAN模块)
linux·驱动·spi·mcp2515
通俗_易懂22 天前
44-RK3588s调试 camera-engine-rkaiq(rkaiq_3A_server)
人工智能·计算机视觉·rk3588·驱动·camera·imx415
肖爱Kun22 天前
LINUX IIC总线驱动-设备框架
驱动
网络研究院1 个月前
微软的 Drasi:一种轻量级的事件驱动编程方法
microsoft·微软·编程·驱动·事件·轻量级·drasi
肖爱Kun1 个月前
内核定时器API实现点灯
驱动
kpler2 个月前
gpio子系统
c语言·驱动
黄卷青灯772 个月前
电脑驱动作用详解
linux·数据库·电脑·驱动