【Linux驱动】字符设备驱动模板(三)—— 初始化 / 加载字符设备

字符设备的注册分为两部分:

  • 注册设备号
  • 注册设备本身

注册设备的基本流程是先初始化要注册的设备,然后将该设备加载到内核。

一、字符设备的类型表示

1、字符设备表示 — struct cdev

在 Linux 中使用 cdev 结构体表示一个字符设备,cdev 结构体及其相关api函数在 include/linux/cdev.h 文件中,结构体的定义如下。

struct cdev {
    struct kobject kobj;
    struct module *owner;                 /* 该设备在内核中 */
    const struct file_operations *ops;    /* 字符设备文件操作函数 */
    struct list_head list;
    dev_t dev;                            /* 设备号 */
    unsigned int count;
};

static struct cdev chrdev;

2、操作集合表示 — struct file_operations

操作集合代表了我们可以对该字符设备进行哪些操作,比如读设备 read、写设备 write、关闭设备 release,我们需要做的就是完善这些函数,前人已经设计出了一套完整的框架,这个框架已经包含了我们所需的大部分函数声明,所以无需我们自己设计。

内核源码中有个 include/linux/fs.h 文件,这个文件定义了一个 file_operations 结构体,该结构体中就包含了今后要实现的函数声明。

下面是操作集合对象的声明与定义。等号左边是 file_operations 的结构体成员,等号右边是我们自己实现的操作函数。

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE, 
	.open = chrdevbase_open,  // chrdevbase_open: 待实现的函数,.open: open操作对应的成员变量
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

二、字符设备操作 API

1、字符设备初始化

创建好字符设备对象以后,就需要对其进行初始化,初始化使用的函数是 cdev_init。函数声明如下:

void cdev_init(struct cdev *cdev, const struct file_operations *fops);

cdev:字符设备指针(对象)

fops:当前字符设备文件操作集合

/* 字符设备 */
static struct cdev dev;		

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE, 
    // .open = chrdevbase_open,   
	// .read = chrdevbase_read,
	// .write = chrdevbase_write,
	// .release = chrdevbase_release,
};    

/* 初始化字符设备 */
chrdev_led.dev.owner = THIS_MODULE;         
cdev_init(&dev, &devfops);					

2、设备加载函数

创建好字符设备以后,我们需要连同设备号,将字符设备加载到内核。字符设备加载函数声明如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count);

p:要添加的字符设备指针

dev:为当前字符设备注册的设备号

count:加载的字符设备的数量

返回值:0 代表成功;其他代表失败

static dev_t devid;                     /* 分配好的设备号 */
static struct cdev dev;		            /* 初始化以后的字符设备 */

ret = cdev_add(&dev, devid, 1);			/* 将字符设备添加到内核 */
if (ret != 0)
{
    return -1;
}

3、设备卸载函数

卸载驱动时,一定要从内核中删除对应的字符设备。使用的函数是 cdev_del,函数原型如下:

void cdev_del(struct cdev *p);

参数 p 就是要删除的字符设备指针。

三、字符设备加载测试

1、手动创建节点

之后会采取自动创建节点的方式,这里为了测试方便,临时采用手动创建节点的方式,需要将驱动模块代码编译加载到内核以后,使用 cat /proc/devices 查看主设备号。(驱动模块代码在最后

insmod chrdevbase.ko    # 加载驱动模块
cat /proc/devices       # 查看主设备号

设备名为 chrdevbase,主设备号为 248,次设备号为0(因为只注册了一个设备,次设备号默认为0)。手动创建节点使用 mknod 命令

# 命令格式:mknod  /dev/设备名  c  主设备号  次设备号
mknod /dev/chrdevbase c 200 0

 

2、应用测试代码

将应用程序代码交叉编译以后得到的执行文件是 chrdevbaseApp,此时在开发板的命令行中输入 

./chrdevbaseApp /dev/chrdevbase

应用程序测试代码如下: 

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

int main(int argc, char* argv[])
{
    char* driver_path = argv[1];       // 位置0 保存的是 ./chrdevbaseApp

    int fd = open(driver_path, O_WRONLY);    // 对应 open 操作
    if (fd < 0)
    {
        perror("open file failed");
        return -2;
    }

    write(fd, (char*)'a', 1);            // 对应 write 操作

    close(fd);                           // 对应 close 操作
    return 0;
}

3、驱动模板代码

#include <linux/module.h>		// MODULE_LICENSE、MODULE_AUTHOR
#include <linux/init.h>			// module_init、module_exit
#include <linux/printk.h>		// printk
#include <linux/kdev_t.h>		// MKDEV
#include <linux/fs.h>			// register_chrdev_region
#include <linux/cdev.h>			// struct cdev

#define CHRDEVBASE_NAME "chrdevbase" 	/* 设备名 */

static struct chrdev_t{
	dev_t devid;			/* 设备号(由主设备号和次设备号组成) */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */

	struct cdev dev;		/* 字符设备 */
} chrdev;

/*
 * @description 	: 打开设备
 * @param – pinode 	: 传递给驱动的 inode
 * @param - pfile 	: 设备文件,file 结构体有个叫做 private_data 的成员变量
 * 一般在 open 的时候将 private_data 指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *pinode, struct file *pfile)
{
    /* 用户实现具体功能 */
	printk("open chrdevbase\n");
    return 0;
}

/*
 * @description 	: 从设备读取数据
 * @param - pfile	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 缓冲区长度
 * @param - offset	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *pfile, char __user *buf, size_t cnt, loff_t *offset)
{
    printk("read chrdevbase\n");
    return 0;
}

/*
 * @description 	: 向设备写数据
 * @param - pfile	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 要给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offset	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *pfile, const char __user *buf, size_t cnt, loff_t *offset)
{
	printk("write chrdevbase\n");
    return cnt;
}

/*
 * @description 	: 关闭/释放设备
 * @param - pfile	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_release (struct inode *pinode, struct file * pfile)
{
    /* 用户实现具体功能 */
	printk("close chrdevbase\n");
    return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevfops = {
	.owner = THIS_MODULE, 
	.open = chrdevbase_open,    // 将chrdevbase_open的函数地址传递给 .open 成员
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*
 * @description	: 驱动入口函数 
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	int ret = 0;
	printk("chrdevbase init!\n");
	/* 1、注册设备号 */
	if (!chrdev.major)
	{
		ret = alloc_chrdev_region(&chrdev.devid, 0, 1, CHRDEVBASE_NAME);
		chrdev.major = MAJOR(chrdev.devid);
		chrdev.minor = MINOR(chrdev.devid);
	}
	else
	{
		chrdev.devid = MKDEV(chrdev.major, 0);
		ret = register_chrdev_region(chrdev.devid, 1, CHRDEVBASE_NAME);
	}

	/* 2、初始化字符设备 */
	chrdev.dev.owner = THIS_MODULE;
	cdev_init(&chrdev.dev, &chrdevfops);

	/* 3、加载字符设备 */
	ret = cdev_add(&chrdev.dev, chrdev.devid, 1);
	if (ret != 0)
	{
		return -1;
	}

	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{
	printk("chrdevbase exit!\n");
	/* 释放设备号 */
	unregister_chrdev_region(chrdev.devid, 1);
	/* 卸载设备号 */
	cdev_del(&chrdev.dev);
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);		// 注册 ko模块被加载到内核,系统会调用的函数
module_exit(chrdevbase_exit);		// 注册 ko模块从内核卸载,系统会调用的函数

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值