指定子设备号创建字符设备

Posted zongzi10010

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了指定子设备号创建字符设备相关的知识,希望对你有一定的参考价值。

目录


title: 指定子设备号创建字符设备
tags: linux
date: 2018/12/28 19:57:24
toc: true
---

指定子设备号字符设备

流程

内核中设备号分为主设备号和次设备号,以前注册字符设备驱动的时候,直接占用了主设备号包含了255个子设备号,也就是内核最多支持255个设备驱动(如果主设备号占据8位)

在Linux2.6中内核可以指定主设备号和对应的子设备号给一个fileoperation,如下图所示

技术分享图片

流程如下:

  1. 确认需要得到几个子设备号使用

  2. 如果指定主设备号使用register_chrdev_region,当返回值小于0,表示注册失败

    dev_t devid;
    devid = MKDEV(major, 0);
    /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
    register_chrdev_region(devid, HELLO_CNT, "hello");
  3. 如果需要系统分配主设备号,alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");当返回值小于0,表示注册失败

    /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
    alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");
    major = MAJOR(devid);
  4. 这里使用 cdev管理驱动,并将file_operations结构体放入cdev-> ops

    cdev_init(&hello_cdev, &hello_fops);
    cdev_add(&hello_cdev, devid, HELLO_CNT);
    
    struct cdev {
           struct kobject    kobj;             // 内嵌的kobject对象 
           struct module   *owner;             //所属模块
           const struct file_operations  *ops; //操作方法结构体
           struct list_head  list; //与 cdev 对应的字符设备文件inode->i_devices 的链表头
           dev_t dev;               //起始设备编号,可以通过MAJOR(),MINOR()来提取主次设备号
           unsigned int count;               //连续注册的次设备号个数
    };
  5. 创建设备文件是与以前一样的,使用class_create创建类和class_device_create创建设备文件

  6. 卸载

    /*将系统中的cdev结构体删除掉*/
    void cdev_del(struct cdev *p); 
    /*注销字符设备*/
    void unregister_chrdev_region(dev_t from, unsigned count);

实例程序

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/cdev.h>

/* 1. 确定主设备号 */
static int major;

static int hello_open(struct inode *inode, struct file *file)
{
    printk("hello_open
");
    return 0;
}

static int hello2_open(struct inode *inode, struct file *file)
{
    printk("hello2_open
");
    return 0;
}

/* 2. 构造file_operations */
static struct file_operations hello_fops = {
    .owner = THIS_MODULE,
    .open  = hello_open,
};

static struct file_operations hello2_fops = {
    .owner = THIS_MODULE,
    .open  = hello2_open,
};


#define HELLO_CNT   2

static struct cdev hello_cdev;
static struct cdev hello2_cdev;
static struct class *cls;

static int hello_init(void)
{
    dev_t devid;
    
    /* 3. 告诉内核 */
#if 0
    major = register_chrdev(0, "hello", &hello_fops); /* (major,  0), (major, 1), ..., (major, 255)都对应hello_fops */
#else
    if (major) {
        devid = MKDEV(major, 0);
        register_chrdev_region(devid, HELLO_CNT, "hello");  /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
    } else {
        alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
        major = MAJOR(devid);                     
    }
    
    cdev_init(&hello_cdev, &hello_fops);
    cdev_add(&hello_cdev, devid, HELLO_CNT);

    devid = MKDEV(major, 2);
    register_chrdev_region(devid, 1, "hello2");
    cdev_init(&hello2_cdev, &hello2_fops);
    cdev_add(&hello2_cdev, devid, 1);
    
#endif

    cls = class_create(THIS_MODULE, "hello");
    class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */
    class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */
    class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */
    class_device_create(cls, NULL, MKDEV(major, 3), NULL, "hello3"); /* /dev/hello3 */
    
    
    return 0;
}

static void hello_exit(void)
{
    class_device_destroy(cls, MKDEV(major, 0));
    class_device_destroy(cls, MKDEV(major, 1));
    class_device_destroy(cls, MKDEV(major, 2));
    class_device_destroy(cls, MKDEV(major, 3));
    class_destroy(cls);

    cdev_del(&hello_cdev);
    unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);

    cdev_del(&hello2_cdev);
    unregister_chrdev_region(MKDEV(major, 2), 1);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

测试

查看到只是用了同一个主设备号

cat  /proc/devices

# cat /proc/devices
Character devices:
252 hello               #同一个主设备号
252 hello2              #同一个主设备号

查看设备文件

# ls /dev/hello* -l
crw-rw----    1 0        0        252,   0 Jan  1 04:18 /dev/hello0
crw-rw----    1 0        0        252,   1 Jan  1 04:18 /dev/hello1
crw-rw----    1 0        0        252,   2 Jan  1 04:18 /dev/hello2
crw-rw----    1 0        0        252,   3 Jan  1 04:18 /dev/hello3
驱动fileopration 次设备号
hello_fops 0,1
hello2_fops 2

也就是对应设备文件的驱动如下

设备文件
/dev/hello0 hello_fops
/dev/hello1 hello_fops
/dev/hello2 hello2_fops
/dev/hello3 没有

使用测试文件去打开文件如下结果

# ./test  /dev/hello0
hello_open                  #驱动1打开的
can open /dev/hello0
# ./test  /dev/hello1
hello_open                  #驱动1打开的
can open /dev/hello1
# ./test  /dev/hello2
hello2_open                 #驱动2打开的
can open /dev/hello2
# ./test  /dev/hello3
can‘t open /dev/hello3      #无法打开

测试程序如下

//arm-linux-gcc -o test test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/*
 * test /dev/hello0
 */

void print_usage(char *file)
{
        printf("%s <dev>
", file);
}

int main(int argc, char **argv)
{
        int fd;
        if (argc != 2)
        {
                print_usage(argv[0]);
                return 0;
        }
        fd = open(argv[1], O_RDWR);
        if (fd < 0)
                printf("can‘t open %s
", argv[1]);
        else
                printf("can open %s
", argv[1]);

        return 0;
}




以上是关于指定子设备号创建字符设备的主要内容,如果未能解决你的问题,请参考以下文章

linux字符设备-自动创建设备号和设备节点

字符设备驱动详解(主次设备号注册/卸载字符设备驱动创建设备节点地址映射)

Linux杂项设备与字符设备

06 字符设备

Linux——Linux驱动之字符类设备驱动编写实践,从0到1手把手教你字符类设备如何申请设备号,如何注册设备,如何自动创建设备节点

Linux——Linux驱动之字符类设备驱动编写实践,从0到1手把手教你字符类设备如何申请设备号,如何注册设备,如何自动创建设备节点