歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

關於字符設備驅動

Linux Device Drivers 筆記

內核模塊框架

最簡單的內核模塊

 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/kernel.h>

 static int __init init_testko(void)
 {
         printk("test ko init\n");
         return 0;
 }

 static void __exit exit_testko(void)
 {
         printk("test ko exit\n");
 }

 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("KEVIN");
 module_init(init_testko);
 module_exit(exit_testko);

Makefile寫法

obj-m = test.o #模塊源代碼

KER_DIR = /root/linux-3.0  #內核源碼路徑
CUR_DIR = $(shell pwd)

all:
        make -C $(KER_DIR) M=$(CUR_DIR) modules

clean:
        rm -rf *.o *.ko *.mod.o *.mod.c *.symvers *.order

cp:
        cp *.ko /root/rootfs/

分配設備號

設備號

在內核中設備號用一個32位數dev_t(linux/types.h)表示,高十二位表示主設備號,低二十位表示次設備號。

#include<linux/kdev_t.h>
MAJOR(dev_t dev);
MINOR(dev_t dev);
以上來兩個宏分別從dev_t中提取主設備號和次設備號。下邊的宏用來合成一個設備號。
MKDEV(int major, int minor);

分配設備號的方法

靜態分配
    int register_chrdev_region(dev_t first, unsigned int count, char * name);
動態分配
    int alloc_chrdev_region(dev_t *dev, unsigned int dirstminor, unsigned int count, char * name);
這兩個函數在分配成功的時候返回0,分配失敗的時候返回一個錯誤碼。
釋放占用的設備號
    void unregister_chrdev_region(dev_t first, unsigned int count);
參數:
  first 要分配的設備編號范圍的啟始值
  count 所請求的連續設備編號的個數
  name  是和該編號范圍關聯的設備名,將出現在/proc/devices和sysfs中
  dev   是用來保存分配的設備號的內存地址

關於錯誤碼,內核定義了一組宏來表示,這些宏保存在./include/asm-generic/errno-base.h中
可以在寫驅動代碼的時候使用,例如
return -EPERM; 表示操作沒有權限,使用的時候需要加上‘-’符號。

關於設備號的分配

內核中有塊設備表和字符設備表,根據設備的類型和主設備號可通過這兩個表之一找到對應的驅動程序函數跳轉結構,而次設備號則一般只用做同類型設備中具體設備項的編號。
linux2.6之前的額版本采用8位的數據類型存儲主設備號,因此將塊設備和字符設備的種類都限制到了256種。

內核源代碼中關於設備號分配最重要的一個函數是__register_chrdev_region,此函數主要維護了一個指針數組chrdevs,以散列表的形式管理設備。而register_chrdev_region和alloc_chrdev_region都間接的調用了這個函數,其中alloc_chrdev_region更是直接以第一個參數為0的方式調用,具體的講解見http://blog.csdn.net/virlhs/article/details/51711112

此處還有一個疑問:在以動態分配形式分配設備號時__register_chrdev_region從255向前搜索chrdevs數組,找到一個指向NULL的指針時就返回此指針的索引(數組下標)作為動態分配的主設備號,但是如果數組chrdevs中的指針全不為NULL,是不是會分配失敗,也就是說動態分配只能分配0-255的主設備號?(先Mark一下,以後探究)

注冊設備

ldd3的方法–關於cdev結構

#include <linux/cdev.h>
struct cdev * cdev_alloc(void);
void cdev_init(struct cdev * dev, struct file_operations * fops);
int cdev_add(struct cdev * dev, dev_t num, unsigned int count);
void cdev_del(struct cdev * dev);

每一個字符設備都對應一個cdev結構,注冊設備的過程就是分配和初始化cdev結構的過程,因此常使用如下代碼完成
動態創建

struct cdev * my_cdev = cdev_alloc();
my_cdev->ops = &my_cdev;
my_cdev->owner = THIS_MODULE;
result = cdev_add(my_cdev, devno, count);

在設備注銷時調用
cdev_del(my_cdev);

靜態創建

struct cdev mcdev;
struct file_operations fops;
cdev_init(&mcdev, &fops);
mcdev.owner = THIS_MODULE;
result = cdev_add(&mycdev, devno, count);

在設備注銷的時候調用
cdev_del(&mcdev);

老方法

int register_chrdev(unsigned int major, const char * name, struct file_operations * fops);
  major : 主設備號,為0時表示自動分配
  name  : 驅動程序的名字,將在/proc/devices中顯示
  fops  : 默認的file_operations結構
int unregister_chrdev(unsigned int major, const char * name);

這種注冊設備的方法是最經常看到的,據說是以前老版本的內核推薦的方法,個人感覺雖然這種方法使用起來方便,但是
卻屏蔽了很多細節,對於學系內核來說並不是很直觀。

使用此種方法注冊的驅動程序,主次設備號都不能大於255

下面看一下幾個函數的調用關系

register_chrdev  
 |---- __register_chrdev  
             |---- __register_chrdev_region  
             |---- cdev_alloc  
             |---- cdev_add

可以看出register_chardev函數封裝了,分配設備號的__register_chrdev_region函數和注冊設備的cdev_alloc函數和cdev_add函數,就是將上邊的過程封裝了起來,

使用設備類自動注冊設備文件

自動注冊設備節點需要mdev的支持,(PC平台叫做udev)mdev會以守護進程的形式運行,在後台監聽sysfs的uevent事件,自動在/dev,目錄下創建相應的設備節點。

為了滿足這樣的需求,根文件系統首先需要相應的支持。
1、必須存在sysfs文件系統 mount -t sysfs sysfs /sys
2、/dev目錄需要掛載為tmpfs mount -t tmpfs mdev /dev
3、在設備開機時啟動mdev, 在/etc/init.d/rcS中添加echo /bin/mdev > /proc/sys/kernel/hotplug && mdev -s

其次代碼中也要添加創建設備節點的相應代碼。

/* This is a #define to keep the compiler from merging different
 * instances of the __key variable */
#define class_create(owner, name)

/**
 * class_destroy - destroys a struct class structure
 * @cls: pointer to the struct class that is to be destroyed
 *
 * Note, the pointer to be destroyed must have been created with a call
 * to class_create().
 */
void class_destroy(struct class *cls)
{
        if ((cls == NULL) || (IS_ERR(cls)))
                return;
        class_unregister(cls);
}

class_create 是一個宏,參數有兩個,owner指所屬模塊,一般為THIS_MODULE,name為設備類名,
此宏調用了 __class_create(owner, name, &__key); 函數,調用關系如下

  __class_create
    | ---- __class_register
              | ---- kset_init
              | ---- kobject_set_name
              | ---- kset_register
                      | ---- kobject_add_internal
                      | ---- kobject_uevent v
              | ---- add_class_attrs
                      | ---- class_create_file
/**
* device_create - creates a device and registers it with sysfs
* @class: pointer to the struct class that this device should be registered to
* @parent: pointer to the parent struct device of this new device, if any
* @devt: the dev_t for the char device to be added
* @drvdata: the data to be added to the device for callbacks
* @fmt: string for the device's name
*
* This function can be used by char device classes.  A struct device
* will be created in sysfs, registered to the specified class.
*
* A "dev" file will be created, showing the dev_t for the device, if
* the dev_t is not 0,0.
* If a pointer to a parent struct device is passed in, the newly created
* struct device will be a child of that device in sysfs.
* The pointer to the struct device will be returned from the call.
* Any further sysfs files that might be required can be created using this
* pointer.
*
* Returns &struct device pointer on success, or ERR_PTR() on error.
*
* Note: the struct class passed to this function must have previously
* been created with a call to class_create().
*/
struct device *device_create(struct class *class, struct device *parent,
                              dev_t devt, void *drvdata, const char *fmt, ...)


/**
* device_destroy - removes a device that was created with device_create()
* @cla: pointer to the struct class that this device was registered with
* @devt: the dev_t of the device that was previously registered
*
* This call unregisters and cleans up a device that was created with a
* call to device_create().
*/
void device_destroy(struct class *class, dev_t devt)

device_createde 函數的調用關系如下:

device_createde
    | ---- device_create_vargs
            | ---- device_register
                      | ---- device_initialize
                      | ---- device_add
                              | ---- kobject_add
                              | ---- device_create_file
                              | ---- device_add_class_symlinks
                              | ---- kobject_uevent

這四個函數的的頭文件都是 #include <linux/device.h>

下面是一個例子

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

#include <linux/cdev.h> // for cdev_add
#include <linux/device.h> // for class_create
#include <linux/err.h>  //for IS_ERR
#include <linux/fs.h>   //for alloc_chrdev_region

static dev_t devno;
static struct cdev * dev;
static struct class * class;
static struct device * device;

static struct file_operations ops = {
    .owner  =   THIS_MODULE,
};

static int __init init_testko(void)
{
    printk("test ko init\n");
    if (alloc_chrdev_region(&devno, 0, 1, "Hello")) {
        printk("device number register failed!\n");
        return  -1;
    }
    printk("device number register success, major : %u\n", MAJOR(devno));

    dev = cdev_alloc();
    dev->owner = THIS_MODULE;
    dev->ops = &ops;
    if (cdev_add(dev, devno, 1)) {
        printk("cdev_add failed!\n");
        unregister_chrdev_region(devno, 1);
        return -1;
    }

    class = class_create(THIS_MODULE, "HELLO");
    if (IS_ERR(class)) {
        cdev_del(dev);
        unregister_chrdev_region(devno, 1);
        return PTR_ERR(class);
    }

    device = device_create(class, NULL,devno, NULL, "hello");
    if (IS_ERR(device)) {
        class_destroy(class);
        cdev_del(dev);
        unregister_chrdev_region(devno, 1);
        return PTR_ERR(device);
    }

    return 0;
}

static void __exit exit_testko(void)
{
    device_destroy(class, devno);
    class_destroy(class);
    cdev_del(dev);
    unregister_chrdev_region(devno, 1);
    printk("test ko exit\n");
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("KEVIN");
module_init(init_testko);
module_exit(exit_testko);

這個實例代碼可以完成使用Insmod命令插入模塊的時候自動在/dev目錄下創建hello設備文件。

Copyright © Linux教程網 All Rights Reserved