register_chrdev函數詳解

一、register_chrdev基本介紹

register_chrdev函數是Linux設備驅動程序中非常重要的函數,它用於動態地分配主設備號和次設備號,並將主設備號和設備驅動程序所用的file_operations結構體綁定在一起。其基本的函數原型如下所示:

int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);

其中major表示主設備號,如果它被設置為0,則表示系統自動分配主設備號。name為設備名稱,通常為設備驅動程序的名稱。fops為設備驅動程序的操作函數指針,它包含了所有與設備相關的操作函數。

二、register_chrdev函數詳解

1. register_chrdev函數的返回值

當register_chrdev函數成功被調用時,它會返回分配的主設備號,否則會返回一個負數,表示分配失敗。當主設備號被成功分配後,你就可以在file_operations結構體中使用這個主設備號了。

下面給出一個簡單的示例代碼,用於演示如何調用register_chrdev函數並檢查它的返回值:

#include <linux/fs.h>
#define MY_MAJOR 0

static int mydev_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "mydev open\n");
    return 0;
}

static int mydev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "mydev release\n");
    return 0;
}

static struct file_operations mydev_fops = {
    .owner = THIS_MODULE,
    .open = mydev_open,
    .release = mydev_release,
};

static int __init mydev_init(void)
{
    int ret;
    
    ret = register_chrdev(MY_MAJOR, "mydev", &mydev_fops);
    if (ret < 0) {
        printk(KERN_ERR "failed to register chrdev: %d\n", ret);
        return ret;
    }
    printk(KERN_INFO "mydev initialized: %d\n", MAJOR);
    return 0;
}

static void __exit mydev_exit(void)
{
    unregister_chrdev(MY_MAJOR, "mydev");
    printk(KERN_INFO "mydev exited\n");
}

module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");

通過上述代碼我們可以看到,在init函數中會調用register_chrdev函數,並檢查返回值是否小於0。如果返回值小於0,則說明分配主設備號失敗。如果分配成功,則可以在設備文件操作函數中使用該主設備號了。

2. register_chrdev與cdev_add函數

register_chrdev函數只是為設備分配主設備號,並將設備文件操作函數與主設備號綁定。在這個過程中,並沒有創建真正的設備文件,也沒有分配次設備號。這時候代碼是不能直接使用文件系統訪問設備的。為了在用戶態能夠訪問設備文件,需要使用cdev_add函數手動在內核中創建設備文件節點,綁定主設備號和次設備號,同時初始化file_operations結構體,例如:

struct cdev mycdev;    // 創建一個cdev對象

int ret = register_chrdev(MY_MAJOR, "mydev", &mydev_fops); // 註冊設備,然後得到主設備號
cdev_init(&mycdev, &mydev_fops);                // 初始化cdev對象,設置mydev_fops結構體
mycdev.owner = THIS_MODULE;

ret = cdev_add(&mycdev, MKDEV(MY_MAJOR, 0), 1); // 在內核中創建設備文件節點,並綁定主設備號和次設備號

通過上面的代碼,我們可以使用該文件操作函數,來訪問到我們的設備,如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main()
{
    int fd;
    fd = open("/dev/mydev", O_RDWR);
    if (fd == -1)
        printf("failed to open\n");
    else
        printf("success to open\n");
    close(fd);
    return 0;
}

通過上面的代碼,我們可以發現,我們已經成功地訪問到了我們的設備了,大家可以嘗試編譯運行一下哦!

3. register_chrdev_region函數

在引入udev後,為了方便使用,我們需要對設備驅動程序編寫更嚴格的規範。register_chrdev_region函數是其中的一個重要函數。在 register_chrdev_region函數中,使用MKDEV函數構造出設備號devno,分配一段設備號區間,並與設備驅動程序的結構體綁定。這樣udev就可以自動地為該設備驅動程序創建設備文件節點了。下面是一段示例代碼:

static int mydev_init(void)
{
    int ret;
    dev_t devno;

    if (MY_MAJOR) {         // 必須顯式地分配主設備號才能使用register_chrdev_region函數
        devno = MKDEV(MY_MAJOR, 0);
        ret = register_chrdev_region(devno, 1, "mydev");
    } else {
        ret = alloc_chrdev_region(&devno, 0, 1, "mydev");
    }

    if (ret < 0) {
        printk(KERN_ERR "failed to register chrdev: %d\n", ret);
        return ret;
    }

    cdev_init(&mycdev, &mydev_fops);
    mycdev.owner = THIS_MODULE;

    ret = cdev_add(&mycdev, devno, 1);
    if (ret < 0) {
        unregister_chrdev_region(devno, 1);
        return ret;
    }

    printk(KERN_INFO "mydev initialized: %d\n", MAJOR);
    return 0;
}

static void __exit mydev_exit(void)
{
    cdev_del(&mycdev);
    unregister_chrdev_region(MKDEV(MY_MAJOR, 0), 1);
    printk(KERN_INFO "mydev exited\n");
}

module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");

4. register_chrdev與class_create函數

class_create函數是對udev庫的簡單封裝,用於創建設備類,和初始化設備的屬性。多個設備驅動程序共用一個設備類。register_chrdev函數中可能涉及到設備的類型,比如常見的字元設備類型是 “cdev”。在設備創建完成後,我們通過class_create定義一個新的設備類,然後通過register_chrdev_register API註冊該設備。如下所示:

struct class *myclass;

static int mydev_init(void)
{
    int ret;
    dev_t devno;

    // ...省略部分代碼
    if (!MY_MAJOR) {
        MY_MAJOR = MAJOR(devno);
    }

    myclass = class_create(THIS_MODULE, "mydev");
    if (IS_ERR(myclass)) {
        unregister_chrdev_region(devno, 1);
        return PTR_ERR(myclass);
    }

    device_create(myclass, NULL, devno, NULL, "mydev");
    return 0;
}

static void __exit mydev_exit(void)
{
    device_destroy(myclass, MKDEV(MY_MAJOR, 0));
    class_destroy(myclass);
    cdev_del(&mycdev);
    unregister_chrdev_region(MKDEV(MY_MAJOR, 0), 1);
    printk(KERN_INFO "mydev exited\n");
}

module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");

上面的代碼中,我們對class_create函數進行了 wrap,並通過device_create API註冊了對應的設備信息。

原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/237797.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-12-12 12:08
下一篇 2024-12-12 12:08

相關推薦

  • Python中引入上一級目錄中函數

    Python中經常需要調用其他文件夾中的模塊或函數,其中一個常見的操作是引入上一級目錄中的函數。在此,我們將從多個角度詳細解釋如何在Python中引入上一級目錄的函數。 一、加入環…

    編程 2025-04-29
  • Python中capitalize函數的使用

    在Python的字元串操作中,capitalize函數常常被用到,這個函數可以使字元串中的第一個單詞首字母大寫,其餘字母小寫。在本文中,我們將從以下幾個方面對capitalize函…

    編程 2025-04-29
  • Python中set函數的作用

    Python中set函數是一個有用的數據類型,可以被用於許多編程場景中。在這篇文章中,我們將學習Python中set函數的多個方面,從而深入了解這個函數在Python中的用途。 一…

    編程 2025-04-29
  • 三角函數用英語怎麼說

    三角函數,即三角比函數,是指在一個銳角三角形中某一角的對邊、鄰邊之比。在數學中,三角函數包括正弦、餘弦、正切等,它們在數學、物理、工程和計算機等領域都得到了廣泛的應用。 一、正弦函…

    編程 2025-04-29
  • 單片機列印函數

    單片機列印是指通過串口或並口將一些數據列印到終端設備上。在單片機應用中,列印非常重要。正確的列印數據可以讓我們知道單片機運行的狀態,方便我們進行調試;錯誤的列印數據可以幫助我們快速…

    編程 2025-04-29
  • Python3定義函數參數類型

    Python是一門動態類型語言,不需要在定義變數時顯示的指定變數類型,但是Python3中提供了函數參數類型的聲明功能,在函數定義時明確定義參數類型。在函數的形參後面加上冒號(:)…

    編程 2025-04-29
  • Python定義函數判斷奇偶數

    本文將從多個方面詳細闡述Python定義函數判斷奇偶數的方法,並提供完整的代碼示例。 一、初步了解Python函數 在介紹Python如何定義函數判斷奇偶數之前,我們先來了解一下P…

    編程 2025-04-29
  • Python實現計算階乘的函數

    本文將介紹如何使用Python定義函數fact(n),計算n的階乘。 一、什麼是階乘 階乘指從1乘到指定數之間所有整數的乘積。如:5! = 5 * 4 * 3 * 2 * 1 = …

    編程 2025-04-29
  • 分段函數Python

    本文將從以下幾個方面詳細闡述Python中的分段函數,包括函數基本定義、調用示例、圖像繪製、函數優化和應用實例。 一、函數基本定義 分段函數又稱為條件函數,指一條直線段或曲線段,由…

    編程 2025-04-29
  • Python函數名稱相同參數不同:多態

    Python是一門面向對象的編程語言,它強烈支持多態性 一、什麼是多態多態是面向對象三大特性中的一種,它指的是:相同的函數名稱可以有不同的實現方式。也就是說,不同的對象調用同名方法…

    編程 2025-04-29

發表回復

登錄後才能評論