Linux驱动之 自动创建设备节点class_create、device_create

Linux驱动之自动创建设备文件节点学习记录:

Linux为系统上的每个设备都创建一种称为节点的特殊文件。与设备的所有通信都通过设备节点完成。
每个节点都有唯一的数值对供Linux内核标识它。
数值对包括一个主设备号和一个次设备号。类的设备被划分到同样的主设备号下。次设备号用于标识主设备组下的某个特定设备。

上节课利用了mknod命令手动创建设备节点,
实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点。
内核中定义了struct class结构体,一个struct class结构体类型变量对应一个类,内核同时提供了class_create()函数,可以用它来创建一个类,这个类存放于sysfs下面,创建了这个类,再调用device_create()函数来在/dev目录下创建相应的设备节点。
这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。

数据结构定义:
#define NEWCHRDEV_CNT	1			/* 设备号个数 */
#define NEWCHRDEV_NAME	"hello"		/* 名字 */

/* newchrdev设备结构体 */
struct newchr_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	 /* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};
struct newchr_dev chr_hello;	/* 设备chr_hello */
class_create
/*
 * @description		: 创建一个class类型的对象
 * @param - owner	: THIS_MODULE
 * @param - name	: 类名字
 * @return 			: 成功,返回struct class的指针;其他 失败
 */
struct class *class_create(struct module *owner, const char *name)

定义一个struct class的指针变量接受返回值,然后通过IS_ERR()判断是否失败,如果成功这个宏返回0,失败返回非0值(可以通过PTR_ERR()来获得失败返回的错误码)

	int result;
	chr_hello.class = class_create(THIS_MODULE, "hlocls"); //生成hlocls设备类
	if (IS_ERR(chr_hello.class)) {
		printk(KERN_ERR "class_create() failed\n");
		result = PTR_ERR(chr_hello.class);
		return result;
	}
device_create
/*
 * @description		: 生成设备节点并导出到用户空间
 * @param - class	: 指向该设备应注册到的struct class的指针(上一个函数)
 * @param - parent	: 指向此新设备的父struct设备的指针(如果有),没有填NULL
 * @param - devt	: 要添加的char设备的dev_t
 * @param - drvdata	: 要添加到设备中以进行回调的数据(如果有),没有填NULL
 * @param - fmt		: 设备名称的字符串
 * @return 			: 成功,返回struct device指针;其他 失败
 */
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)

定义一个struct device的指针变量接受返回值,然后通过IS_ERR()判断是否失败,如果成功这个宏返回0,失败返回非0值(可以通过PTR_ERR()来获得失败返回的错误码)

	chr_hello.device = device_create(chr_hello.class, NULL, chr_hello.devid, NULL, NEWCHRDEV_NAME);
	if (IS_ERR(chr_hello.device)) {
		printk(KERN_ERR "device_create() failed\n");
		result = PTR_ERR(chr_hello.device);
		return result;
	}
alloc_chrdev_region
/*
 * @description	     : 注册一个范围()的设备号
 * @param - dev		 : cdev结构体地址
 * @param - baseminor: 起始的次设备号
 * @param -	count 	 :次设备号个数
 * @param -	name	 :设备名称
 * @return 			 : 成功返回0,失败返回错误码(负数)
 */		
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
file_operations
/* 设备操作函数 */
static struct file_operations chr_hello_ops = {
	.owner = THIS_MODULE,	
	.open = hello_open,
	.release = hello_release,
};

open、release对应应用层的open()、close()函数。实现比较简单,直接返回0即可。
其中read、write、unloched_ioctrl 函数的实现需要涉及到用户空间和内存空间的数据拷贝。
在Linux操作系统中,用户空间和内核空间是相互独立的。也就是说内核空间是不能直接访问用户空间内存地址,同理用户空间也不能直接访问内核空间内存地址。
如果想实现,将用户空间的数据拷贝到内核空间或将内核空间数据拷贝到用户空间,就必须借助内核给我们提供的接口来完成。
下一篇文章实现read、write接口。

vim hello.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>

#include <linux/uaccess.h>

#define NEWCHRDEV_CNT	1			/* 设备号个数 */
#define NEWCHRDEV_NAME	"hello"		/* 名字 */

/* newchrdev设备结构体 */
struct newchr_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	 /* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};
struct newchr_dev chr_hello;	/* 设备hello */

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int hello_open (struct inode *inode, struct file *filep)
{
	printk("hello_open()\n");
	return 0;
}
/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int hello_release (struct inode *inode, struct file *filep)
{
	printk("hello_release()\n");
	return 0;
}

/* 设备操作函数 */
static struct file_operations chr_hello_ops = {
	.owner = THIS_MODULE,	
	.open = hello_open,
	.release = hello_release,
};

/* 
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int hello_init(void)
{
	int result = 0;
	printk("chrdev_hello init!\r \n");
	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (chr_hello.major) {		/*  定义了设备号 */
		chr_hello.devid = MKDEV(chr_hello.major, 0);
		/* 据定义设备号申请注册 */
		result = register_chrdev_region(chr_hello.devid, NEWCHRDEV_CNT, NEWCHRDEV_NAME);
		if(result < 0){
			printk("register_chrdev fail \n");  
			goto out_err_1;
		}
	} else {			/* 没有定义设备号,自动分配*/
		result = alloc_chrdev_region(&chr_hello.devid, 0, NEWCHRDEV_CNT, NEWCHRDEV_NAME);	/* 申请设备号 */
		if(result < 0){
			printk("alloc_chrdev_region fail \n"); //自动分配设备号错误
			goto out_err_1;
		}
		chr_hello.major = MAJOR(chr_hello.devid);	/* MAJOR宏获取分配号的主设备号 */
		chr_hello.minor = MINOR(chr_hello.devid);	/* MINOR宏获取分配号的次设备号 */
	}
	printk("chr_hello major=%d,minor=%d\r\n",chr_hello.major, chr_hello.minor);	
	
	/* 2、初始化cdev */
	chr_hello.cdev.owner = THIS_MODULE;
	cdev_init(&chr_hello.cdev, &chr_hello_ops);
	/* 3、添加一个cdev */
	cdev_add(&chr_hello.cdev, chr_hello.devid, NEWCHRDEV_CNT);

	/* 4、创建类 */
	chr_hello.class = class_create(THIS_MODULE, "hlocls"); //生成hlocls设备类
	if (IS_ERR(chr_hello.class)) {
		printk(KERN_ERR "class_create() failed\n");
		result = PTR_ERR(chr_hello.class);
		goto out_err_2;
	}

	/* 5、创建设备 */
	chr_hello.device = device_create(chr_hello.class, NULL, chr_hello.devid, NULL, NEWCHRDEV_NAME);
	if (IS_ERR(chr_hello.device)) {
		printk(KERN_ERR "device_create() failed\n");
		result = PTR_ERR(chr_hello.device);
		goto out_err_3;
	}
	return result; 
//释放已申请的资源返回
out_err_3:
	device_destroy(chr_hello.class, chr_hello.devid); /*  删除device */	
out_err_2:
	class_destroy(chr_hello.class);  /*  删除class */
	unregister_chrdev_region(chr_hello.devid, NEWCHRDEV_CNT); /* 注销设备号 */
	cdev_del(&chr_hello.cdev);/*  删除cdev */
out_err_1:
	return 	result; 
}
/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void hello_exit(void)
{
	printk("chrdev_hello exit!\r \n");
	/* 注销字符设备驱动 */
	device_destroy(chr_hello.class, chr_hello.devid); /*  删除device */
	class_destroy(chr_hello.class);  /*  删除class */
	unregister_chrdev_region(chr_hello.devid, NEWCHRDEV_CNT); /* 注销设备号 */
	cdev_del(&chr_hello.cdev);/*  删除cdev */
	return;
}
//modinfo  name.ko
MODULE_LICENSE("GPL"); //遵循GPL协议
MODULE_AUTHOR("CJX");
MODULE_DESCRIPTION("Just for Demon");

module_init(hello_init);
module_exit(hello_exit);
//cat proc/devices

make、insmod

root@ubuntu16:/home/cxx/driver/5_class# 
root@ubuntu16:/home/cxx/driver/5_class# make
"1st"
make -C /lib/modules/4.15.0-142-generic/build M=/home/cxx/driver/5_class modules
make[1]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic'
"2nd"
  CC [M]  /home/cxx/driver/5_class/hello.o
  Building modules, stage 2.
"2nd"
  MODPOST 1 modules
  CC      /home/cxx/driver/5_class/hello.mod.o
  LD [M]  /home/cxx/driver/5_class/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'
root@ubuntu16:/home/cxx/driver/5_class# ls
a.out  app_cdev.c  hello.c  hello.ko  hello.mod.c  hello.mod.o  hello.o  Makefile  modules.order  Module.symvers
root@ubuntu16:/home/cxx/driver/5_class# lsmod | grep hello
root@ubuntu16:/home/cxx/driver/5_class#
root@ubuntu16:/home/cxx/driver/5_class# dmesg -c
root@ubuntu16:/home/cxx/driver/5_class# insmod hello.ko

app_cdev.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
	int fd;
	fd = open("/dev/hello",O_RDWR);
	if(fd<0)
	{
		perror("open fail \n");
		return -1;
	}
	printf("cdev_hello open success\n ");
	close(fd);
	return 0;
}

./a.out

root@ubuntu16:/home/cxx/driver/5_class# gcc app_cdev.c 
root@ubuntu16:/home/cxx/driver/5_class# ./a.out 
cdev_hello open success
 root@ubuntu16:/home/cxx/driver/5_class# dmesg
[48973.889676] hello_open()
[48973.889714] hello_release()
root@ubuntu16:/home/cxx/driver/5_class# 

查看节点文件生成:

cxx@ubuntu16:~$ 
cxx@ubuntu16:~$ dmesg 
[ 3303.145506] hello_init 
cxx@ubuntu16:~$ 
cxx@ubuntu16:~$ cd /dev/
cxx@ubuntu16:/dev$ 
cxx@ubuntu16:/dev$ ls -l | grep hello 
crw-------  1 root root    222,   0 Apr 24 23:04 hello //设备节点文件
cxx@ubuntu16:/dev$ cd /sys/class/
cxx@ubuntu16:/sys/class$ ls -l |grep hlocls 
drwxr-xr-x 2 root root 0 Apr 24 23:04 hlocls //创建的类
cxx@ubuntu16:/sys/class$ 
modinfo hello.ko
root@ubuntu16:/home/cxx/driver/class# modinfo hello.ko
filename:       /home/cxx/driver/class/hello.ko
license:        GPL
description:    Just for Demon
author:         CJX
srcversion:     80CCDA080F5EC6D49A43853
depends:        
retpoline:      Y
name:           hello
vermagic:       4.15.0-142-generic SMP mod_unload 
root@ubuntu16:/home/cxx/driver/class#