简单的字符驱动

目录

功能点

具体实现

Makefile编写

驱动加载

字符驱动测试程序

测试程序编译

Cmakelist编写


功能点

1、支持基于字符设备文件进行发送和接收数据;

2、支持阻塞式接收数据,没有数据的情况下,等待数据就绪;

具体实现

#include "vEth.h"
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/slab.h>

#define ETH_NAME "vEth0" /* 设备名 */

struct veth_device_st {
    const char *name;
    struct device *dev;
    struct miscdevice *miscdev;
    wait_queue_head_t read_queue;
    wait_queue_head_t write_queue;
};

static struct veth_device_st *veth_device;
DEFINE_KFIFO(my_kfifo, char, 1024*1024*1024);

/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般app在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int eth_open(struct inode *inode, struct file *file)
{
    int major = MAJOR(inode->i_rdev);
    int minor = MINOR(inode->i_rdev);

    pr_info("%s: major=%d, minor=%d\n", __func__, major, minor);

    file->private_data = veth_device;
    pr_info("%s: file->private_data=0x%p \n", __func__, file->private_data);

    return 0;
}

/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t eth_read(struct file *file, char __user *buf, size_t count, loff_t *offt)
{
    int ret;
    unsigned int copied_count = 0;
    struct veth_device_st *veth_device = file->private_data;
    /*
    * (1)add for O_NONBLOCK reading
    */
    if(kfifo_is_empty(&my_kfifo)){
        if(file->f_flags & O_NONBLOCK) {
            return  -EAGAIN;
        }
        // (2)若用户发起的是非BLOCK读,则因为空间为空,调度进程到等等队列,进程睡眠
        ret = wait_event_interruptible(veth_device->read_queue, !kfifo_is_empty(&my_kfifo));
        if (ret) {
            return ret;
        }
    }

    ret = kfifo_to_user(&my_kfifo, buf,  count, &copied_count);
    if(ret != 0) {
        return -EFAULT;
    }

    // (3)如果FIFI有空余,唤醒等待写入的其他进程
    if (!kfifo_is_full(&my_kfifo)) {
        wake_up_interruptible(&veth_device->write_queue);
    }

    return copied_count;

}

/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t eth_write(struct file *file, const char __user *buf, size_t count, loff_t *offt)
{
    int ret;
    unsigned int copied_count=0;
    struct veth_device_st *data = file->private_data;

    /*
    * (1)add for O_NONBLOCK writing
    */
    if (kfifo_is_full(&my_kfifo)) {
        if (file->f_flags & O_NONBLOCK){
            return  -EAGAIN;
        }

        // (2)若用户发起的是非BLOCK写,则因为空间已满,调度进程到等等队列,进程睡眠
        ret = wait_event_interruptible(data->write_queue, !kfifo_is_empty(&my_kfifo));
        if (ret) {
            return ret;
        }
    }

    ret = kfifo_from_user(&my_kfifo, buf, count, &copied_count);
    if(ret != 0) {
        return -EFAULT;
    }
    // (3)如果数据非空,唤醒等待读取的其他进程
    if (!kfifo_is_empty(&my_kfifo)) {
        wake_up_interruptible(&data->read_queue);
    }

    return copied_count;
}

/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int eth_release(struct inode *inode, struct file *file)
{
    return 0;
}


/*
* 设备操作函数结构体
*/
static struct file_operations eth_fops = {
        .owner = THIS_MODULE,
        .open = eth_open,
        .read = eth_read,
        .write = eth_write,
        .release = eth_release,
};

static struct miscdevice misc_device = {
        .minor = MISC_DYNAMIC_MINOR,
        .name = ETH_NAME,
        .fops = &eth_fops,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 0 成功;其他 失败
*/
static int __init eth_init(void)
{
    int ret;

    pr_info("eth_init\n");
    if (veth_device != NULL) {
        return 0;
    }

    //(1) 创建共享的数据结构,包含IO读和写的等待队列
    veth_device = kmalloc(sizeof(struct veth_device_st), GFP_KERNEL);
    if (!veth_device) {
        pr_err("failed to kmalloc");
        return -ENOMEM;
    }

    veth_device->miscdev = &misc_device;
    init_waitqueue_head(&veth_device->read_queue);
    init_waitqueue_head(&veth_device->write_queue);

    ret = misc_register(&misc_device);
    if (ret != 0 ) {
        pr_err("failed to misc_register");
        return ret;
    }
    return 0;
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit eth_exit(void)
{
    pr_info("eth_exit\n");
    if(veth_device) {
        kfree(veth_device);
    }
    misc_deregister(&misc_device);
}

module_init(eth_init);
module_exit(eth_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("test");

Makefile编写

KERNELDIR := /lib/modules/$(shell uname -r)/build
CURRENT_PATH := $(shell pwd)
obj-m := vEth.o

build: kernel_modules

kernel_modules:
   $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
   $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

驱动加载

sudo insmod vEth.ko

字符驱动测试程序

#include <stdio.h>

#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[] = {"test data!"};
int main(int argc, char* argv[]) {

    int fd, retvalue;//fd 文件描述符
    char *filename;
    char readbuf[100], writebuf[100];

    if(argc != 3){//传参个数不为3,说明主设备号等缺少,因此不能执行应用
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /* 打开驱动文件 open API
     通过传参 判断要使用哪个驱动文件 以及命令是什么
      */
    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);
        }
        printf("write file retvalue %d !\r\n", retvalue);
    }
    printf("=======================\n");
    /* 关闭设备 */
    retvalue = close(fd);
    if(retvalue < 0) {
        printf("Can't close file %s\r\n", filename);
        return -1;
    }
    return 0;
}

测试程序编译

本测试测试程序是基于Cmake进行编译的,也可以直接使用gcc编译。

Cmakelist编写

cmake_minimum_required(VERSION 3.22)
project(test_eth C)

set(CMAKE_C_STANDARD 99)

add_executable(test_eth main.c)

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值