字符设备驱动注册与设备节点创建----函数讲解与代码示例

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yk150915/article/details/84980337

1、字符设备编号注册

内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。

(1)register_chrdev 比较老的内核注册的形式,早期的驱动使用。
(2)register_chrdev_region/alloc_chrdev_region + cdev 新的驱动形式

区别
(1)register_chrdev()函数是老版本里面的设备号注册函数,可以实现静态和动态注册两种方法,主要是通过给定的主设备号是否为0来进行区别,为0的时候为动态注册。
(2)register_chrdev_region以及alloc_chrdev_region就是将上述函数的静态和动态注册设备号函数进行了拆分。
(3)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
(4)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。

使用方法
函数原型:register_chrdev_region(dev_t dev, unsigned int count, char *name)
dev :要分配的设备编号范围的初始值, 这组连续设备号的起始设备号
count:连续编号的个数,是这组设备号的大小(也是次设备号的个数)
name:编号相关联的设备名称
分配成功时返回值为0,否则返回错误编号。

函数原型:alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
让内核自动给我们分配设备号,baseminor设置为0即可。

参数解析
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

(1)这个函数的第一个参数,是输出型参数,获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,将主设备号和次设备号,提取打印出来,看自动分配的是多少。
(2)第二个参数:次设备号的基准,从第几个次设备号开始分配。
(3)第三个参数:次设备号的个数。
(4)第四个参数:驱动的名字。
(5)返回值:小于0,则错误,自动分配设备号错误。否则分配得到的设备号就被第一个参数带出来。

设备号转化宏
在内核中,dev_t类型(linux/types.h中)用来保存设备编号,包括主设备号和次设备号。dev_t是一个32位的数,其中的12位用来表示主设备号,在其余的20位表示次设备号。

操作设备号需要使用linux/kdev_t.h中的宏,比如要获得dev_t的主设备号或次设备号,应使用
MAJOR(dev_t dev)
MINOR(dev_t dev)

相反,如果需要将主设备号和次设备号转换成dev_t类型,则使用
MKDEV(int major, int minor)

2、字符设备注册到内核

头文件应该包含linux/cdev.h

申请cdev:
struct cdev *my_cdev =cdev_alloc();
my_cdev->ops = &my_fops;

初始化已经分配到的结构
void cdev_init(struct cdev *cdev, const struct file_operations *fops)

通知内核该结构的信息
int cdev_add(struct cdev *p, dev_t dev, unsigned count)

释放cdev申请的内存
void cdev_del(struct cdev *p)

实际cdev_alloc()函数和cdev_init(struct cdev *cdev, const struct file_operations *fops)函数的实现基本一致,可以去搜索函数的源码实现。cdev_alloc只是没有给cdev->ops赋值fops,并且返回创建的指针。在上传的代码中两个函数都使用了,你在使用中可以任选其一。

3、注册设备节点

头文件在include/linux/device.h中

内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,在/sys/class/下创建类目录,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。

函数原型:
struct class *class_create(struct module *owner, const char *name)
struct device *device_create(struct class *class, struct device *parent, dev_t devt, const char *fmt, …)

4、驱动代码示例

本代码已经验证过可行:

/**
 ******************************************************************************
 * @file    Kernel_test hello.c
 * @author  Carl.yang
 * @version V0.0.1
 * @date    10-12-2018
 * @brief   main entrance.
 ******************************************************************************
 */
  
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/device.h>

struct cdev *hello_cdev = NULL;
dev_t devID = 0;
static struct proc_dir_entry *g_proc_dir;
static struct class *pClass;
static struct device *pDev;

static int hello_proc_show(struct seq_file *m, void *v)
{
    seq_printf(m, "v0.01\n");
    return 0;
}

static int hello_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, hello_proc_show, NULL);
}

static const struct file_operations hello_proc_fops = {
    .owner = THIS_MODULE,
    .open = hello_proc_open,
    .read = seq_read,
    .release = single_release,
};

static int proc_create_entry(void)
{
    struct proc_dir_entry *proc_show_entry;

	printk(KERN_ALERT"proc initialized.\n");

	g_proc_dir = proc_mkdir("hello", 0);
	if (g_proc_dir == 0)
        {
		printk(KERN_ALERT"proc fail 1.\n");
		return -1;
	}
	proc_show_entry =  proc_create("fw_version", 0644, g_proc_dir, &hello_proc_fops);
    if (proc_show_entry == 0)
    {
		printk(KERN_ALERT"proc fail 2.\n");
		return -1;
    }
    return 0;
}

static ssize_t hello_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk(KERN_ALERT"HELLO READ!\n");  
	return 0;
}
static ssize_t hello_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	printk(KERN_ALERT"HELLO write!\n");  
	return 0;
}

static int hello_open(struct inode *inode, struct file *filp)
{
	printk(KERN_ALERT"HELLO OPEN!\n");
	return 0;
}
static const struct file_operations hello_fops = {
	.owner =	THIS_MODULE,
	.write =	hello_write,
	.read =		hello_read,
	.open =		hello_open,
};

static int hello_major = 111;
static int __init hello_init(void)  
{  
    int ret = -1;
    
    devID = MKDEV(hello_major, 0);
    
    /*--1--*/
    ret = register_chrdev_region(devID, 1, "hello_chrdev");
    if(ret)
    {
        printk(KERN_ALERT"fail to register chrdev %d, allocate chrdev again.\n", MAJOR(devID));
        ret = alloc_chrdev_region(&devID, 0, 1, "hello_chrdev");
        if(ret)
        {
            printk(KERN_ALERT"fail to allocate chrdev.\n");
            return ret;
        }
    }
    printk(KERN_ALERT"major number : %d.\n", MAJOR(devID));
    
    /*--2--*/
    hello_cdev = cdev_alloc();
    if(hello_cdev == NULL)
    {
        printk(KERN_ALERT"hello_cdev alloc fail.\n");  
    }
    hello_cdev->ops = &hello_fops;
    
    /*--3--*/
    cdev_init(hello_cdev, &hello_fops);
    hello_cdev->owner = THIS_MODULE;
    
    /*--4--*/
    ret = cdev_add(hello_cdev, devID, 1);
    if(ret)
        goto err1;
    
    /*create node*/
    pClass = class_create(THIS_MODULE, "hello_chrdev");
	if (IS_ERR(pClass))
        {
		printk(KERN_ALERT"class create fail, error code(%ld).\n", PTR_ERR(pClass));
		goto err1;
	}
    
    pDev = device_create(pClass, NULL, devID, NULL, "hello");
	if (IS_ERR(pDev))
       {
		printk(KERN_ALERT"device create fail, error code(%ld).\n", PTR_ERR(pDev));
		goto err2;
	}
    
    proc_create_entry();
    
    return 0;
    
err2:
	if (pClass)
        {
		class_destroy(pClass);
		pClass = NULL;
	}

    
err1:
    cdev_del(hello_cdev);
    unregister_chrdev_region(devID, 1);

    return -1;  
}  
   
static void __exit hello_exit(void)  
{  
    cdev_del(hello_cdev);
    unregister_chrdev_region(devID, 1);
    remove_proc_entry("hello_proc", NULL);
    printk(KERN_ALERT"Goodbye,nice to see you.\n");  
}  
  
module_init(hello_init);  
module_exit(hello_exit);  
  
MODULE_LICENSE("Dual BSD/GPL");  
MODULE_AUTHOR("Carl.yang");  

对应的makefile文件


EXTRA_CFLAGS=-fno-pic

ifeq ($(KERNELRELEASE),)
	KERNELDIR := /lib/modules/$(shell uname -r)/build
	PWD := $(shell pwd)
modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
	rm -rf *.o *~core.depend .*.cmd *.mod.c *.tmp_version *.order *.symvers
else
	obj-m := hello.o
endif

应用空间测试代码

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

int main(int argc, char **argv)
{
	int fd;
	int val = 1;
	char buf[50];
    
	fd = open("/dev/hello", O_RDWR);
	if(fd < 0)
		printf("can't open!\r\n");
    
	read(fd, buf, 20);
	write(fd, &val, 4);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/yk150915/article/details/84980337
今日推荐