linux內核字符設備驅動之讀操作

linux應用程序read函數的使用

char rbuf[1024] = {0}; //分配緩衝區,保存讀取的數據;
ret = read(fd, rbuf, 1024);//讀設備,將從設備讀取的數據保存在rbuf緩衝區中,要讀1024字節;
ret保存實際讀取的字節數;

底層驅動的read接口

struct file_operations {
    ssize_t (*read) (struct file *file, 
                        char __user *buf, 
                        size_t count, 
                        loff_t *ppos);
}; 

read接口作用:用於讀取設備,並且將讀取的數據上報給用戶;
與應用程序read的調用關係;
應用程序調用read->…->調用驅動read接口
參數:
file:文件指針
buf:保存用戶緩衝區的首地址(rbuf),在驅動程序中不能直接訪問這個buf(*buf = 1不允許),如果驅動程序要向用戶緩衝區寫入數據,必須利用內核提供的內存拷貝函數,將內核驅動的數據拷貝到用戶緩衝區中;
count:用戶要讀取的字節數,例如1024字節
ppos:保存讀的位置信息,例如
獲取上一次的讀位置:
loff_t pos = *ppos;
假如這次成功讀了1024字節;
更新讀位置信息:
*ppos = pos + 1024;

切記:對於read接口的第二個參數buf,這個buf指針保存的是用戶緩衝區的首地址,在內核空間不能直接訪問操作,需要利用內核的內存拷貝函數,將內核的數據拷貝到用戶緩衝區中,這個內存拷貝函數:

unsigned long copy_to_user(void __user *to, 
                          const void *from, 
                          unsigned long n)

作用:將內核驅動的數據拷貝到用戶緩衝區中
參數:
to:目的地址,傳遞用戶緩衝區的首地址(buf)
from:源地址,傳遞內核緩衝區的首地址
n:要拷貝的字節數
只要看到__user修飾的指針,就不能在驅動中直接訪問操作,必須利用內存拷貝函數

編寫字符設備驅動,提供read接口,將內核數據讀取到用戶空間

1.mkdir /opt/drivers/2.0
2.cd /opt/drivers/2.0
3.vim led_drv.c
4.vim led_test.c
5.vim Makefile
6.make
7.arm-linux-gcc -0 led_test led_test.c
8.cp led_test led_drv.ko /opt/rootfs

ARM:
1.insmod led_drv.ko
2.cat /proc/devices //獲取主設備號
3.mknod /dev/myled c 主設備號 0
4../led_test
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h> //struct file_operations
#include <linux/cdev.h> //struct cdev + 設備號
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/uaccess.h> //copy_to_user

//聲明描述LED硬件相關的數據結構
struct led_resource
{
    char *name;
    int gpio;
};

//定義初始化LED硬件信息
static struct led_resource led_info[] =
{
    [0] = {
        .name = "LED1",
        .gpio = S5PV210_GPC0(3)
    },
    [1] = {
        .name = "LED2",
        .gpio = S5PV210_GPC0(4)
    }
};

//定義設備號
static dev_t dev;

//定義字符設備對象
static struct cdev led_cdev;

//調用關係:應用程序open->....->led_open
static int led_open(struct inode *inode, struct file *file)
{
    int i;

    for(i = 0; i < ARRAY_SIZE(led_info); i++)
        gpio_set_value(led_info[i].gpio, 1);

    printk("%s\n", __func__);
    return 0; //執行成功返回0,執行失敗返回負值
}

//調用關係:應用程序close->...->led_close
static int led_close(struct inode *inode, struct file *file)
{
    int i;

    for(i = 0; i < ARRAY_SIZE(led_info); i++)
        gpio_set_value(led_info[i].gpio, 0);

    printk("%s\n", __func__);
    return 0; //執行成功返回0,執行失敗返回負值
}

//調用關係:應用程序read->...->led_read
static ssize_t led_read(struct file *file,
                        char __user *buf,
                        size_t count,
                        loff_t *ppos)
{
    //定義初始化內核緩衝區(存儲空間再後1G虛擬內存中)
    int kdata = 0x5555;

    //將內核數據上報給用戶
    //切記:buf雖然保存的用戶緩衝區的首地址,但不能直接訪問
    //*(int *)buf = kdata;錯誤
    copy_to_user(buf, &kdata, sizeof(kdata));
    printk("%s\n", __func__);
    return sizeof(kdata); //失敗返回負值,成功返回實際讀取的字節數
}

//定義初始化硬件操作方法
static struct file_operations led_fops =
{
    .owner = THIS_MODULE,
    .open = led_open, //打開設備
    .release = led_close, //關閉設備
    .read = led_read //讀取設備
};

static int led_init(void)
{
    int i;
    //申請設備號
    alloc_chrdev_region(&dev, 0, 1, "LEDREAD");

    //初始化字符設備對象
    cdev_init(&led_cdev, &led_fops);

    //註冊字符設備對象到內核
    cdev_add(&led_cdev, dev, 1);

    //申請GPIO資源和配置GPIO爲輸出口,輸出0(省電)
    for (i = 0; i < ARRAY_SIZE(led_info); i++)
        {
            gpio_request(led_info[i].gpio, led_info[i].name);
            gpio_direction_output(led_info[i].gpio, 0);
        }
    return 0;
}

static void led_exit(void)
{
    int i;

    //輸出0,釋放GPIO資源
    for (i = 0; i < ARRAY_SIZE(led_info); i++)
        {
            gpio_set_value(led_info[i].gpio, 0);
            gpio_free(led_info[i].gpio);
        }
    //卸載字符設備對象
    cdev_del(&led_cdev);
    //釋放設備號
    unregister_chrdev_region(dev, 1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

應用程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
    int fd;
    int udata;  //定義用戶緩衝區

    //打開設備
    //open->....->調用led_open
    fd = open("/dev/myled", O_RDWR);
    if (fd < 0) {
        printf("打開設備失敗!\n");
        return -1;
    }

    read(fd, &udata, sizeof(udata));
    printf("從驅動讀取的數據爲:udata = %#x\n", udata);

    //關閉設備
    //close->...->調用led_close
    close(fd);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章