Linux驱动开发 - 字符设备驱动

驱动程序

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#define CHRDEVBASE_MAJOR    200             /* 主设备号 */
#define CHRDEVBASE_NAME     "chrdevbase"    /* 设备名     */
​
static char readbuf[100];       /* 读缓冲区 */
static char writebuf[100];      /* 写缓冲区 */
char kerneldata[] = {"kernel data!"};
​
/*
 * @description     : 打开设备
 * @param - inode   : 传递给驱动的inode
 * @param - filp    : 设备文件,file结构体有个叫做private_data的成员变量
 *                    一般在open的时候将private_data指向设备结构体。
 * @return          : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    //printk("chrdevbase open!\r\n");
    return 0;
}
​
/*
 * @description     : 从设备读取数据 
 * @param - filp    : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt    : 相对于文件首地址的偏移
 * @return          : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    
    /* 向用户空间发送数据 */
    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    retvalue = copy_to_user(buf, readbuf, cnt);
    if(retvalue == 0){
        printk("kernel senddata ok!\r\n");
    }else{
        printk("kernel senddata failed!\r\n");
    }
    
    //printk("chrdevbase read!\r\n");
    return 0;
}
​
/*
 * @description     : 向设备写数据 
 * @param - filp    : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt    : 相对于文件首地址的偏移
 * @return          : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    /* 接收用户空间传递给内核的数据并且打印出来 */
    retvalue = copy_from_user(writebuf, buf, cnt);
    if(retvalue == 0){
        printk("kernel recevdata:%s\r\n", writebuf);
    }else{
        printk("kernel recevdata failed!\r\n");
    }
    
    //printk("chrdevbase write!\r\n");
    return 0;
}
​
/*
 * @description     : 关闭/释放设备
 * @param - filp    : 要关闭的设备文件(文件描述符)
 * @return          : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    //printk("chrdevbase release!\r\n");
    return 0;
}
​
/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,   
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};
​
/*
 * @description : 驱动入口函数 
 * @param       : 无
 * @return      : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
    int retvalue = 0;
​
    /* 注册字符设备驱动 */
    retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
    if(retvalue < 0){
        printk("chrdevbase driver register failed\r\n");
    }
    printk("chrdevbase init!\r\n");
    return 0;
}
​
/*
 * @description : 驱动出口函数
 * @param       : 无
 * @return      : 无
 */
static void __exit chrdevbase_exit(void)
{
    /* 注销字符设备驱动 */
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
    printk("chrdevbase exit!\r\n");
}
​
/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
​
/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lk");
​
​
​

chrdevbase_open 函数,当应用程序调用 open 函数的时候此函数就会调用

chrdevbase_read 函数,应用程序调用 read 函数从设备中读取数据的时候此函 数会执行。参数 buf 是用户空间的内存,读取到的数据存储在 buf 中,参数 cnt 是要读取的字节 数,参数 offt 是相对于文件首地址的偏移。 kerneldata 里面保存着用户空间要读取的数据

kerneldata 数组中的数据拷贝到读缓冲区 readbuf 中,第 52 行通过函数 copy_to_user 将 readbuf 中的数据复制到参数 buf 中。因为内核空间不能直接操作用户空间的内存,因此需要借 助 copy_to_user 函数来完成内核空间的数据到用户空间的复制 。

chrdevbase_write 函数,应用程序调用 write 函数向设备写数据的时候此函数 就会执行。参数 buf 就是应用程序要写入设备的数据,也是用户空间的内存,参数 cnt 是要写入 的数据长度,参数 offt 是相对文件首地址的偏移。

chrdevbase_release 函数,应用程序调用 close 关闭设备文件的时候此函数会 执行,一般会在此函数里面执行一些释放操作。

驱动入口函数 chrdevbase_init 函数 register_chrdev 用来注册字符设备

驱动出口函数 chrdevbase_exit 函数 unregister_chrdev 用来注销字符设备。

通过 module_initmodule_exit 这两个函数来指定驱动的入口和出口函数。

用户程序

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
static char usrdata[] = {"usr data!"};
​
int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    char readbuf[100], writebuf[100];
​
    if(argc != 3){
        printf("Error Usage!\r\n");
        return -1;
    }
​
    filename = argv[1];
​
    /* 打开驱动文件 */
    fd  = open(filename, O_RDWR);
    if(fd < 0){
        printf("Can't open file %s\r\n", filename);
        return -1;
    }
​
    if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */
        retvalue = read(fd, readbuf, 50);
        if(retvalue < 0){
            printf("read file %s failed!\r\n", filename);
        }else{
            /*  读取成功,打印出读取成功的数据 */
            printf("read data:%s\r\n",readbuf);
        }
    }
​
    if(atoi(argv[2]) == 2){
    /* 向设备驱动写数据 */
        memcpy(writebuf, usrdata, sizeof(usrdata));
        retvalue = write(fd, writebuf, 50);
        if(retvalue < 0){
            printf("write file %s failed!\r\n", filename);
        }
        else{
            /*  读取成功,打印出读取成功的数据 */
            printf("write data:%s\r\n",usrdata);
        }
    }
​
    /* 关闭设备 */
    retvalue = close(fd);
    if(retvalue < 0){
        printf("Can't close file %s\r\n", filename);
        return -1;
    }
​
    return 0;
}

数组 usrdata 是测试 APP 要向 chrdevbase 设备写入的数据。

一共有三个参数“./chrdevbaseApp”、“/dev/chrdevbase”和“1”,这三个参数分别对应 argv[0]、argv[1]和 argv[2]。第一个参数表示运行 chrdevbaseAPP 这个软件,第二个参数表示测试APP要打开/dev/chrdevbase这个设备。第三个参数就是要执行的操作, 1表示从 chrdevbase 中读取数据, 2 表示向 chrdevbase 写数据。

调用 C 库中的 open 函数打开设备文件: /dev/chrdevbase。

运行测试

  • 加载驱动模块

    • mount -t nfs -o nolock,nfsvers=3,vers=3 192.168.100.103:/home/lk/nfs /mnt/

    • insmod chrdevbase.ko

  • 创建设备节点文件

    • mknod /dev/chrdevbase c 200 0

  • chrdevbase 设备操作测试

    • ./chrdevbaseApp /dev/chrdevbase 1

      read data:kernel data!

    • ./chrdevbaseApp /dev/chrdevbase 2

      kernel recevdata:usr data!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值