Linux驱动编程 - 字符设备驱动


目录

简介:

一、字符设备驱动框架

1、字符设备驱动入口

2、字符设备驱动加载过程

2.1 申请设备号

2.1.1 分配设备号函数

(1) 静态分配函数

(2) 动态分配函数

(3) 注销设备号

2.1.2 设备号中的主/次设备号

2.1.3 申请设备号示例

 

2.2 注册字符设备

2.2.1 cdev结构体介绍

 

2.2.2 file_operations结构体介绍

 

2.2.3 初始化、注册字符设备

(1) cdev_init

(2) cdev_add

(3) 注销字符设备

2.2.4 注册字符设备示例

 

2.3 自动创建设备节点

2.3.1 class_create创建类

2.3.2 device_create创建设备

2.3.3 自动创建设备节点示例

二、字符设备示例

1、命令测试

 

2、应用程序读写


简介:


Linux内核主要包括三种驱动模型,字符设备驱动、块设备驱动以及网络设备驱动。本文主要介绍字符设备驱动的框架和机制。

一、字符设备驱动框架


Linux底层驱动一般都从入口函数开始分析。

1、字符设备驱动入口


驱动首先实现的就是加载和卸载函数,也是驱动程序的入口函数。

 static int __init xxx_init(void)
 {
 ​
 }
 ​
 static void __exit xxx_exit(void)
 {
     
 }
 ​
 module_init(xxx_init);
 module_exit(xxx_exit);

这段代码实现了通用驱动的 module_init  加载与 module_exit  卸载框架。

2、字符设备驱动加载过程


  • 字符设备驱动加载过程

  • 字符设备驱动卸载过程

2.1 申请设备号


每一类字符设备都有唯一的设备号,其中设备号又分为主设备号次设备号。

  • 主设备号:用于标识设备的类型,如:区分是IIC设备还是SPI设备;

  • 次设备号:用于区分同类型的不同设备,如:区分IIC设备下,具体哪一个设备,是MPU6050还是EEPROM

2.1.1 分配设备号函数

设备号的分配方式有两种,一种是静态分配,另一种是动态分配。

(1) 静态分配函数

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

功能:以from设备号开始,连续分配count个同类型的设备号

参数:

  • from:表示已知的一个设备号

  • count:表示连续设备编号的个数,(同类型的设备有多少个)

  • name:表示设备或者驱动的名称

(2) 动态分配函数

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

功能:从baseminor次设备号开始,连续分配count个同类型的设备号,并自动分配一个主设备号,将主、次组成的设备号信息赋值给*dev

参数:

  • dev:设备号的指针,用于存放分配的设备号,可使用MAJOR和MINOR宏,将主设备号和次设备号分别提取出来;

  • baseminor:次设备号从第几开始分配;

  • count:表示连续次设备号的个数,(同类型的设备有多少个);

  • name:表示设备或者驱动的名称;

两个函数的区别

  • register_chrdev_region:已给定主设备号和次设备号,调用该函数将设备号注册到内核;

  • alloc_chrdev_region:未给定设备的主设备号和次设备号,调用后,主设备号以0来表示,以自动分配,并且将自动分配的设备号,同样加入到子系统中,方便系统追踪系统设备号的使用情况;

(3) 注销设备号

设备号作为一种系统资源,使用完后需要将占用的设备号归还给系统,调用 unregister_chrdev_region 注销

void unregister_chrdev_region(dev_t from, unsigned count)

功能:要注销from主设备号下的连续count个设备

参数:

  • from:表示已知的一个设备号

  • count:表示连续设备编号的个数,(同类型的设备有多少个)

2.1.2 设备号中的主/次设备号

设备号的类型是dev_t ,内核 include/linux/types.h 可以看到dev_t 表示u32类型的数值。

 typedef u32 __kernel_dev_t;​
 typedef __kernel_dev_t      dev_t;

dev_t 包括主设备号和次设备号,由 MINORBITS 宏定义指定分界线,主设备号占用12bit次设备号占用20bit

头文件 include/linux/kdev_t.h 中定义

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))    //从dev_t中获取主设备号
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))     //从dev_t中获取次设备号
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))         //主/次设备号组成dev_t

2.1.3 申请设备号示例

static dev_t devid;				//设备号
static int major;				//主设备号
static int minor;				//次设备号
#define DEV_CNT        1        //设备数量
#define DEV_NAME    "test"      //设备名字

/* 创建设备号 */
if (major) {         /* 定义了设备号 */
    devid = MKDEV(major, 0);        //主设备号major和次设备号0组成dev_t 
    register_chrdev_region(devid, DEV_CNT, DEV_NAME);     //dev_t设备号注册到内核
} else {             /* 没有定义设备号 */
    alloc_chrdev_region(&devid, 0, DEV_CNT, DEV_NAME);    //申请设备号dev_t 
    major = MAJOR(devid);     //获取dev_t的主设备号
    minor = MINOR(devid);     //获取dev_t的次设备号
}

2.2 注册字符设备


2.2.1 cdev结构体介绍

cdev 定义在 include/linux/cdev.h,用来抽象一个字符设备。

struct cdev{
	struct kobject kobj;                //表示一个内核对象
	struct module *owner;	            //值为THIS_MODULE,表示模块
	const struct file_operations *ops;	//操作集,包括open、read、write等操作接口
	struct list_head list;              //用于将该设备加入到内核模块链表中
	dev_t dev;	                        //设备号(包括主设备号和次设备号)
	unsigned int count;	                //次设备号个数
};
2.2.2 file_operations结构体介绍

file_operations 定义在 include/linux/fs.h,它是文件操作集,包含open、read、write等操作。

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 *);
	int (*iterate_shared) (struct file *, struct dir_context *);
	__poll_t (*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 *);
	unsigned long mmap_supported_flags;
	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 (*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
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
			u64);
	int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
			u64);
	int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
2.2.3 初始化、注册字符设备

(1) cdev_init

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

功能:初始化cdev结构体

参数:

  • cdev:struct cdev结构体字符设备

  • fops:文件操作集

(2) cdev_add

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

功能:添加一个字符设备到kernel

参数:

  • p:cdev结构体指针

  • dev:设备号

  • count:该类型设备的个数

(3) 注销字符设备

void cdev_del(struct cdev *p)

功能:从系统中移除该字符设备驱动

2.2.4 注册字符设备示例

调用 cdev_add 后,"cat /proc/devices" 命令能看到字符设备信息

/* 定义file_operations */
static struct file_operations dev_fops = {
	.owner = THIS_MODULE,
	.open = dev_open,
	.read = dev_read,
	.write = dev_write,
	.release = 	dev_release,
};

/* 初始化并注册 cdev */
    struct cdev cdev;
    cdev.owner = THIS_MODULE;
    cdev_init(&cdev, &dev_fops);		//初始化字符设备结构,设置file_operations
    cdev_add(&cdev, devid, DEV_CNT);    //添加至内核,"cat /proc/devices"能查看

2.3 自动创建设备节点


mknod 命令可以手动创建设备节点,简单介绍如下:

$ mknod /dev/test c 252 0        #生成/dev/test,c表示字符设备,主设备号为 252,次设备号为 0

主设备号可以用 "cat /proc/devices" 查看。下面主要介绍自动创建设备节点。

2.3.1 class_create创建类

#define class_create(owner, name)       \

({                      \

    static struct lock_class_key __key; \

    __class_create(owner, name, &__key);    \

})

void class_destroy(struct class *cls)                //注销class

调用class_create后,会在/sys/class/下创建类文件夹,文件夹名由函数的第二个入参决定。

2.3.2 device_create创建设备

struct device *device_create(struct class *class, struct device *parent,

                 dev_t devt, void *drvdata, const char *fmt, ...)

功能:调用device_create,会在/sys/class/xxx类下面创建一个设备,通过udev在/dev/目录下生成设备文件节点,文件名由fmt参数决定。

参数:

  • class:表示设备要创建哪个类下面;
  • parent:父设备, 一般为 NULL, 也就是没有父设备;
  • devt:设备号;
  • drvdata:设备可能会使用的一些数据, 一般为 NULL;
  • fmt:设备名字, 如果设置 fmt=xxx 的话, 就会生成/dev/xxx 这个设备文件;

返回值:创建好的设备

删除设备时调用:

void device_destroy(struct class *class, dev_t devt)        //注销device        


udev工作过程如下:
(1)当内核检测到系统中出现新设备后,内核会通过netlink套接字发送uevent;
(2)设备模块加载时udev获取内核发送的信息,扫描/sys/class/下的设备目录进行规则匹配。从而在/dev/创建设备节点;
 

2.3.3 自动创建设备节点示例

static struct class *class;		/* 类 		*/
static struct device *device;	/* 设备		*/

/* 4、创建类 */
    class = class_create(THIS_MODULE, "test");	///sys/class/目录下会创建一个新的文件夹
    if (IS_ERR(class)) {
        return PTR_ERR(class);
    }

    /* 5、创建设备 */
    device = device_create(class, NULL, devid, NULL, "test");//dev目录下创建相应的设备节点
    if (IS_ERR(device)) {
        return PTR_ERR(device);
    }

二、字符设备示例


#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

static dev_t devid;						/* 设备号	*/
static int major;						/* 主设备号	*/
static int minor;						/* 次设备号	*/
static struct cdev cdev;				/* cdev		*/
static struct class *class;				/* 类 		*/
static struct device *device;			/* 设备		*/

#define CDEV_CNT		1		  		/* 设备号个数 */
#define CDEV_NAME		"cdev_test"		/* 名字 */

static int chardev_open(struct inode *inode, struct file *filp)
{
	//filp->private_data = &cdev_data;  /* 设置私有数据 */
	printk("open...\n");
	return 0;
}

static ssize_t chardev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	printk("read...\n");
	return 0;
}

static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	printk("write...\n");
    return cnt;
}

static int chardev_release(struct inode *inode, struct file *filp)
{
	printk("release...\n");
	return 0;
}

/* 设备操作函数 */
static struct file_operations chardev_fops = {
	.owner = THIS_MODULE,
	.open = chardev_open,
	.read = chardev_read,
	.write = chardev_write,
	.release = 	chardev_release,
};

/* 入口函数 */
static int __init chardev_init(void)
{
/* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (major) { /* 定义了设备号 */
        devid = MKDEV(major, 0);
        register_chrdev_region(devid, CDEV_CNT, CDEV_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&devid, 0, CDEV_CNT, CDEV_NAME); /* 申请设备号 */
        major = MAJOR(devid); /* 获取分配号的主设备号 */
        minor = MINOR(devid); /* 获取分配号的次设备号 */
    }
    printk("major=%d, minor=%d\r\n", major, minor);

    /* 2、初始化 cdev */
    cdev.owner = THIS_MODULE;
    cdev_init(&cdev, &chardev_fops);				//file_operations

    /* 3、添加一个 cdev */
    cdev_add(&cdev, devid, CDEV_CNT);

    /* 4、创建类 */
    class = class_create(THIS_MODULE, CDEV_NAME);	///sys/class/目录下会创建一个新的文件夹
    if (IS_ERR(class)) {
        return PTR_ERR(class);
    }

    /* 5、创建设备 */
    device = device_create(class, NULL, devid, NULL, CDEV_NAME);//dev目录下创建相应的设备节点
    if (IS_ERR(device)) {
        return PTR_ERR(device);
    }
    return 0;
}

/* 出口函数 */
static void __exit chardev_exit(void)
{
    /* 注销字符设备驱动 */
    cdev_del(&cdev); /* 删除 cdev */
    unregister_chrdev_region(devid, CDEV_CNT); /* 注销 */

    device_destroy(class, devid);
    class_destroy(class);
}

module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("donga");

本示例read、write函数只打印信息演示作用。在实际使用中,read函数通常会调用 copy_to_user 将内核空间的数据拷贝到用户空间,write函数则调用  copy_from_user 将用户空间数据拷贝到内核空间,再做处理。

1、命令测试


$ insmod chardev_drv.ko        #加载驱动,生成/dev/cdev_test设备节点

$ cat /proc/devices                 #查看字符设备

$ cat  /dev/cdev_test                   #读

$ echo "1" > /dev/cdev_test        #写

/ # cat /dev/cdev_test                    #读,过程:open->read->release
open...
read...
release...

/ # echo "1" > /dev/cdev_test        #写,过程:open->write->release
open...
write...
release...

2、应用程序读写


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

int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	
	if(argc != 2){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	fd = open(filename, O_RDWR);    //打开设备文件
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	write(fd, NULL, 0);       //写
	read(fd, NULL, 0);        //读

	retvalue = close(fd);     //关闭文件
	if(retvalue < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	return 0;
}

执行过程:


  • 28
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值