linux驅(qū)動開發(fā)(三):Linux字符設(shè)備驅(qū)動實例

上一篇我們介紹了字符設(shè)備架構(gòu)的大概原理、常用的數(shù)據(jù)結(jié)構(gòu)和函數(shù)。接下來,我們撰寫一個簡單的驅(qū)動程序和用戶程序,使用戶程序可以通過open、release、read、write等常用的文件操作函數(shù),來完成對驅(qū)動程序的打開、關(guān)閉、讀寫等控制。

編寫代碼前,我們再梳理一下思路。想要實現(xiàn)上述效果,我們的工作主要分為三大部分:

  1. 編寫驅(qū)動程序,并加載到內(nèi)核中,等待被用戶程序調(diào)用。

  2. 在console控制臺下使用mknod命令創(chuàng)建一個設(shè)備節(jié)點。

  3. 編寫用戶程序,通過調(diào)用設(shè)備節(jié)點間接控制驅(qū)動程序。

這三部分每一部分實現(xiàn)的具體步驟如下,下面這張圖只涉及到了模塊注冊時的操作和初始化操作,其他操作沒有反映出來。

下面我們來逐步實現(xiàn)一過程。

一、注冊設(shè)備號

首先使用cat /proc/device我們找到一個未被占用的主設(shè)備號,這里我們使用233作為新注冊設(shè)備的主設(shè)備號。

注冊設(shè)備號的操作在驅(qū)動程序中,通過調(diào)用register_chrdev_region函數(shù)完成。因此我們創(chuàng)建一個驅(qū)動程序cdev_driver.c,寫入下面的代碼。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zz");

static int major = 233; //主設(shè)備號
static int minor = 0;   //一系列次設(shè)備號中的第一個次設(shè)備號
static dev_t devno;     //設(shè)備號

static int demo_init(void)
{
    int rc;
    devno = MKDEV(major, minor);    //根據(jù)主設(shè)備號和次設(shè)備號合成設(shè)備號
    rc = register_chrdev_region(devno, 1, "test");  //向系統(tǒng)中注冊設(shè)備號
    if(rc < 0) {
        pr_err("register_chrdev_region failed!");
        return rc;
    }
    return 0;
}

static void demo_exit(void)
{
    unregister_chrdev_region(devno, 1); //向系統(tǒng)中注銷設(shè)備號
    return;
}

module_init(demo_init);
module_exit(demo_exit);

編寫Makefile。

ifneq ($(KERNELRELEASE),)
    obj-m := cdev_driver.o
else
    KDIR    := /lib/modules/$(shell uname -r)/build
    PWD     := $(shell pwd)
all:
    make -C $(KDIR) M=$(PWD) modules
clean:
    rm -rf *.ko *.o *.mod.o *.symvers *.cmd *.mod.c *.order *.dwo *.mod.dwo *.mod
endif

編譯該文件,并在系統(tǒng)中加載該驅(qū)動模塊,可以看到系統(tǒng)中233的設(shè)備號被分配給名為“test”的設(shè)備。


二、實現(xiàn)file_operations結(jié)構(gòu)體

這一步同樣在驅(qū)動程序cdev_driver.c中,我們需要首先新建一個file_operations結(jié)構(gòu)體fops,并實現(xiàn)其中的open、release、read、write函數(shù)。

#define KMAX_LEN 32

static int demo_open(struct inode *ind, struct file *fp)
{
    printk("demo open\n");
    return 0;
}

static int demo_release(struct inode *ind, struct file *fp)
{
    printk("demo release\n");
    return 0;
}

static ssize_t demo_read(struct file *fp, char __user *buf, size_t size, loff_t *pos)
{
    int rc;
    char kbuf[KMAX_LEN] = "read test\n"; 
    if (size > KMAX_LEN)
        size = KMAX_LEN;
    
    rc = copy_to_user(buf, kbuf, size); //將字符串kbuf拷貝到用戶空間下的buf中
    if(rc < 0) {
        return -EFAULT;
        pr_err("copy_to_user failed!");
    }
    return size;
}

static ssize_t demo_write(struct file *fp, const char __user *buf, size_t size, loff_t *pos)
{
    int rc;
    char kbuf[KMAX_LEN];
    if (size > KMAX_LEN)
        size = KMAX_LEN;

    rc = copy_from_user(kbuf, buf, size);   //將用戶空間下的buf中的內(nèi)容拷貝到內(nèi)核空間中
    if(rc < 0) {
        return -EFAULT;
        pr_err("copy_from_user failed!");
    }
    printk("%s", kbuf);
    return size;
}

static struct file_operations fops = {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
};

file_operations結(jié)構(gòu)體中的成員都是函數(shù)指針,我們的工作就是編寫四個函數(shù)demo_open、demo_release、demo_read、demo_write,并將它們分別賦值給.open、.release、.read、.write四個函數(shù)指針。其中demo_open和demo_release兩個函數(shù)比較簡單,打印兩句話即可,demo_read和demo_write分別實現(xiàn)讀寫操作。demo_read用于將內(nèi)核中定義的字符串“read test”讀出到buf中,并返回實際讀出的字符串長度size;demo_write用于向內(nèi)核中寫入一個長度為size的字符串buf。size不能超過限定的最大長度KMAX_LEN。

由于讀出和寫入操作是跨越用戶空間和內(nèi)核空間的,所以需要使用copy_to_user和copy_from_user兩個函數(shù)。前者用來從內(nèi)核空間拷貝到用戶空間,后者反之。

三、注冊字符設(shè)備結(jié)構(gòu)體

在寫好file_operations結(jié)構(gòu)體后,就可以調(diào)用cdev_init和cdev_add函數(shù),對字符設(shè)備結(jié)構(gòu)體進行初始化并加入系統(tǒng)了。在模塊卸載時,需要調(diào)用cdev_del移除字符設(shè)備結(jié)構(gòu)體。最終得到的完整的驅(qū)動程序的代碼如下。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zz");

static int major = 233;
static int minor = 0;
static dev_t devno;

#define KMAX_LEN 32

static int demo_open(struct inode *ind, struct file *fp)
{
    printk("demo open\n");
    return 0;
}

static int demo_release(struct inode *ind, struct file *fp)
{
    printk("demo release\n");
    return 0;
}

static ssize_t demo_read(struct file *fp, char __user *buf, size_t size, loff_t *pos)
{
    int rc;
    char kbuf[KMAX_LEN] = "read test\n"; 
    if (size > KMAX_LEN)
        size = KMAX_LEN;
    
    rc = copy_to_user(buf, kbuf, size);
    if(rc < 0) {
        return -EFAULT;
        pr_err("copy_to_user failed!");
    }
    return size;
}

static ssize_t demo_write(struct file *fp, const char __user *buf, size_t size, loff_t *pos)
{
    int rc;
    char kbuf[KMAX_LEN];
    if (size > KMAX_LEN)
        size = KMAX_LEN;

    rc = copy_from_user(kbuf, buf, size);
    if(rc < 0) {
        return -EFAULT;
        pr_err("copy_from_user failed!");
    }
    printk("%s", kbuf);
    return size;
}

static struct file_operations fops = {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
};

static struct cdev cd;

static int demo_init(void)
{
    int rc;
    devno = MKDEV(major, minor);
    rc = register_chrdev_region(devno, 1, "test");
    if(rc < 0) {
        pr_err("register_chrdev_region failed!");
        return rc;
    }

    cdev_init(&cd, &fops);          //初始化字符設(shè)備結(jié)構(gòu)體
    rc = cdev_add(&cd, devno, 1);   //將字符設(shè)備結(jié)構(gòu)體加入系統(tǒng)中
    if (rc < 0) {
        pr_err("cdev_add failed!");
        return rc;
    }
    return 0;
}

static void demo_exit(void)
{
    cdev_del(&cd);  //從系統(tǒng)中刪除字符設(shè)備結(jié)構(gòu)體
    unregister_chrdev_region(devno, 1);
    return;
}

module_init(demo_init);
module_exit(demo_exit);

四、應(yīng)用程序編寫

驅(qū)動程序完全編寫完畢,下面編寫應(yīng)用程序。在應(yīng)用程序中,我們要完成下面的操作:使用open函數(shù)打開字符設(shè)備,使用read函數(shù)從字符設(shè)備中讀出字符串“read test”,使用write函數(shù)向字符設(shè)備中寫入字符串“write test”,使用close釋放字符設(shè)備。我們將設(shè)備節(jié)點的名稱命名為test_chr_dev。代碼如下。

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

int main()
{
    int rc;
    char buf[32];
    int fd = open("/dev/test_chr_dev", O_RDWR);
    if (fd < 0) {
        printf("open file failed!\n");
        return -1;
    }
    read(fd, buf, 32);
    printf("%s", buf);
    write(fd, "write test\n", 32);
    close(fd);
    return 0;
}

使用gcc對代碼進行編譯。

gcc app.c -o app

五、程序運行

進入root模式,首先使用mknod指令向系統(tǒng)中創(chuàng)建設(shè)備節(jié)點。


查看dev目錄可以看到新增加的設(shè)備節(jié)點。

然后加載驅(qū)動模塊,再運行應(yīng)用程序??梢钥吹阶x功能正常。


dmesg打印一下內(nèi)核信息,可以看到設(shè)備打開、寫功能、釋放正常。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容