1. class结构体介绍

内核中定义了struct class结构体,一个struct class 结构体类型变量对应一个类,内核同时提供了class_create()函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建了这个类,再调用device_create()函数在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create()函数,去/sysfs下寻找对应的类而创建设备节点。
struct class结构体(2.6.26.6内核版本)

struct class {
    const char *name;
    struct module *owner;
    struct class_attribute *class_attrs;
    struct device_attribute *dev_attrs;
    struct bin_attribute *dev_bin_attrs;
    struct kobject *dev_kobj;

    int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
    char *(*devnode)(struct device *dev, mode_t *mode);
    void (*class_release)(struct class *class);
    void (*dev_release)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);

    const struct kobj_ns_type_operations *ns_type;
    const void *(*namespace)(struct device *dev);

    const struct dev_pm_ops *pm;
    struct subsys_private *p;
};

继续阅读

1. 结构体

内核中每个字符设备都对应一个 cdev结构的变量,下面是它的定义:

linux-2.6.22/include/linux/cdev.h
struct cdev {
    struct kobject kobj;                // 每个 cdev都是一个 kobject
    struct module *owner;               //指向实现驱动的模块
    const struct file_operations *ops;  // 操纵这个字符设备文件的方法
    struct list_head list;              // 与 cdev对应的字符设备文件的inode->i_devices的链表头
    dev_t dev;                          // 起始设备编号
    unsigned int count;                 // 设备范围号大小
};

继续阅读

1. 字符设备结构体

内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里。该散列表中的每一个元素是一个 char_device_struct 结构,它的定义如下:

   static struct char_device_struct {
       struct char_device_struct *next;    // 指向散列冲突链表中的下一个元素的指针
       unsigned int major;                 // 主设备号
       unsigned int baseminor;             // 起始次设备号
       int minorct;                        // 设备编号的范围大小
       char name[64];                      // 处理该设备编号范围内的设备驱动的名称
       struct file_operations *fops;       // 没有使用
       struct cdev *cdev;                  // 指向字符设备驱动程序描述符的指针
   } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

继续阅读

大概步骤

一、 注册设备号

# 注册函数:
    regsiter_chrdev_region()
    alloc_chrdev_region() 或           查看#cat /proc/devices
    register_chrdev()
# 注销函数:
    unregist_chrdev_region() 或
    unregister_chrdev()

二、初始化cdev并添加到系统

# 初始化cdev
    cdev_init()   // 静态初始化
    cdev_alloc()  // 动态初始化
# 添加到系统函数
    cdev_add()
# 从系统删除函数
    cdev_del()

三、创建设备节点

# 创建类
    class_create()  // 将放于/sysfs      查看#ls /sys/class
# 删除类
    class_destroy()
        
# 创建节点
    device_create() 或 class_device_create()  // 将存放于/dev  查看#ls /dev
# 删除节点
    device_destroy() 或 class_device_destroy()

继续阅读

在字符设备驱动开发的入门教程中,最常见的就是用device_create()函数来创建设备节点了,但是在之后阅读内核源码的过程中却很少见device_create()的踪影了,取而代之的是device_register()与device_add(),将device_create()函数展开不难发现:其实device_create()只是device_register()的封装,而device_register()则是device_add()的封装。
继续阅读

解决上一篇遗留问题
第一个问题的解决方案很简单,注册驱动程序的时候,如果传入的major为0,那么系统将会自动为这个驱动程序分配主设备号,同时这个程序也会返回所分配的主设备号。
第二个问题,解决起来也不是很困难,在Linux中提供了一种机制是udev,可以用于自动的创建设备,在嵌入式Linux的文件系统,比如busybox,也有一套简化版的机制,是mdev,在配置文件系统的时候会进行相应的配置,写完了关于文件系统的文章,我会将链接贴上来。
我们如果在调用驱动程序的入口函数的时候,就使用mdev来创建这个设备文件其实就行了。使用mdev的时候,需要用到两个结构体,一个是class和class_device。这两个结构体,都定义在%kernel%/include/linux/device.h的头文件中

继续阅读

1. first_drv.c

#include <linux/init.h>     //定义了module_init
#include <linux/module.h>   //最基本的头文件,其中定义了MODULE_LICENSE这一类宏
#include <linux/fs.h>       // file_operations结构体定义在该头文件中

static const char* dev_name = "first_drv";  //  定义设备名
static unsigned int major = 55;               //定义设备号
static int dev_id;

//定义了open函数
static int first_drv_open (struct inode *inode, struct file *file)
{
    printk("first_drv_open\n");
    return 0;
}

//定义了write函数
static ssize_t first_drv_write (struct file *file, const char __user *buf, size_t size, loff_t * ppos)
{
    printk("first_drv_write\n");
    return 0;
}

//在file_operations中注册open和write函数
static struct file_operations first_drv_fo =
{
    .owner  =  THIS_MODULE,

    //将对应的函数关联在file_operations的结构体中
    .open   =  first_drv_open,
    .write  =  first_drv_write,
};

//init驱动的入口函数
static int first_drv_init(void)
{
    //注册设备,实际是将file_operations结构体放到内核的制定数组中,以便管理
    //在register_chrdev中制定major作为主设备号
    dev_id = register_chrdev(major, dev_name , &first_drv_fo);
    printk("first_drv_init\n");
    if(dev_id < 0)
        printk("init_error\n");
    return 0;
}

//驱动的出口函数
static void first_drv_exit(void)
{
    printk("first_drv_exit\n");
    unregister_chrdev(major, dev_name);  //卸载设备,实际是将file_operations结构体从内核维护的相关数组中以主设备号作为索引删除
}

//内核将通过这个宏,来直到这个驱动的入口和出口函数
module_init(first_drv_init);
module_exit(first_drv_exit);

MODULE_AUTHOR("zhou");
MODULE_LICENSE("GPL");  //指定协议

继续阅读

linux设备驱动_HelloWorld的实现

1. hello.c文件

包含的头文件

#include <linux/module.h> //这个头文件包含了许多符号与函数的定义,这些符号与函数多与加载模块有关
#include <linux/init.h> //这个头文件包含了你的模块初始化与清除的函数
//#include <linux/moduleparam.h> //如果你的模块需要用到参数传递,那么你可能就要声明moduleparam.h这个头文件

描述性声明

继续阅读