Android-驅動學習-入門

基礎概念:

設備號 : 分爲主設備號和次設備號。設備號是16bit, 高8bit爲主設備號,低8bit爲次設備號。當我們創建一個設備節點時需要指定主設備號和次設備號。應用程序通過名稱訪問設備,而設備號指定了對應的驅動程序和對應的設備。主設備號標識設備對應的驅動程序,次設備號由內核使用,用於確定設備節點所指設備。你可以通過/proc/devices文件來查看系統設備的主設備號。

設備節點: Linux中設備節點是通過“mknod”命令來創建的。一個設備節點其實就是一個文件,Linux中稱爲設備文件。一般都創建在dev/目錄下。

在userspace中可以打開設備節點,並通過驅動向設備節點寫入數據。具體怎麼實現呢?

1.kernel層的實現

a) 找到一個設備號devno,可以動態申請,也可以靜態設定,假設靜態設定爲major,minor,通過宏MKDEV(major,minor)來生成devno

b) 構建對設備的操作函數集file_opreation結構體,裏面包含了的設備的操作:open、read、write、release、ioctl等

c) 構建cdev結構體,裏面填充兩個主要成員dev(設備號)、file_operation(對設備的操作)

d) 把cdev添加到cdev鏈表中:cdev_init、cdev_add

具體代碼:

首先, 內核在初始化的時候會調用initcall.init中保存的一份函數指針,並在初始化完成之後釋放整個init區段。

當需要把函數xxx_init放到initcall.init區段的函數指針表中時,只需要聲明: core_initcall(xxx_init);即可。

module_exit(xxx_exit)和前面的過程相似,在退出的時候會執行所有放在exit表中的函數。

所以一個驅動,最先執行的代碼就是放在init列表裏面的xxx_init函數。

我們看看這個函數做了什麼:
 

static int __init xxx_init(void)

{
    dev_t dev;
    struct xxx_dev xxxdriver = kzalloc(sizeof(struct xxx_dev) + 5, GFP_KERNEL); // 先定義一個driver
   // 給driver中的變量賦值
    xlogdriver->buf = kzalloc(XLOGBUF_SIZE, GFP_KERNEL);
    xlogdriver->num = 1;
    xlogdriver->name = "xxx";
    xlogdriver->free_size = XLOGBUF_SIZE;

   alloc_chrdev_region(&dev, xlogdriver->minor_start, xlogdriver->num, xlogdriver->name);
    xlogdriver->cdev = cdev_alloc();

    ret = xxx_setup_cdev(dev); // 然後調用setup函數!!!
}

// 構建對設備的操作函數集file_opreation結構體

static const struct file_operations xxxfops = {
.owner = THIS_MODULE,
.read = xxx_read,
.write = xxx_write,
.poll = xxx_poll,
.open = xxx_open,
.release = xxx_close
};
static int xxx_setup_cdev(dev_t devno)

{
    cdev_init(xxxdriver->cdev, &xxxfops); // 初始化cdev
    xxxdriver->cdev->owner = THIS_MODULE;
    xxxdriver->cdev->ops = &xxxfops; // cdev的主要成員,對設備節點的操作

    err = cdev_add(xxxdriver->cdev, devno, 1); // 把cdev添加到cdev鏈表中

    xxxdriver->xxx_class = class_create(THIS_MODULE, "xxx");
    xlogdriver->xlog_dev = device_create(xlogdriver->xxx_class,NULL, devno, (void *)xlogdriver, "xlog");

    return 0;
}

然後重點實現的函數就是file_operations xxxfops中構建的函數xxx_read、xxx_write、xxx_poll、xxx_open和xxx_close。

實現完成之後userspace就可以通過這些函數去對xxx這個設備節點進行操作了。

eg:

static ssize_t xxx_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)

{
    int err = 0;
    size_t copy_bytes;
    u64 temp = count;
    mutex_lock(&xxxdriver->xxx_mutex); // 寫入之前先上鎖
    if (xxxBUF_SIZE < xxxdriver->writeindex + count) {
        copy_bytes = xxxBUF_SIZE - xxxdriver->writeindex;
        err = copy_from_user(xxxdriver->buf + xxxdriver->writeindex, buf, copy_bytes); // 從userspace的buf中讀取數據寫入xxxdriver->buf
        err = copy_from_user(xxxdriver->buf, buf + copy_bytes, count - copy_bytes);
        xxxdriver->writeindex = count - copy_bytes;
    } else {
        err = copy_from_user(xxxdriver->buf + xxxdriver->writeindex, buf, count);
        xxxdriver->writeindex += count;
    }
    xxxdriver->free_size -= count; // 更新剩餘空間
    mutex_unlock(&xxxdriver->xxx_mutex); // 寫完之後解鎖
    wake_up_interruptible(&xxxdriver->wait_q);
    return count;
}

2. userspace的操作

int fd=open("/dev/xxx",O_RDWR)來打開設備文件,此設備對應有一個設備號,這是我們識別驅動和設備的橋樑。

打開 /dev/xxx時,根據設備號,在cdev鏈表中找到cdev這個結構體,cdev裏面包含了file_operation結構體,有設備的各種操作,打開時就調用裏面的.open 函數。

具體代碼:

例如此時app有一些數據需要寫入設備節點:

void wirte_to_xxx(char* str){
    int fd = open("/dev/xxx",O_RDWR); // 先打開設備節點,此處最終調用的就是xxx_open
    write(fd, str, strlen(str)); // 寫入數據, 此處最終調用的就是xxx_write
    close(fd); // 寫完之後關閉節點,此處最終調用的就是xxx_close
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章