Linux驱动开发04:块设备驱动和网络设备驱动

2023-05-16

介绍

因为块设备驱动和网络设备驱动实际中用得较少,所以只给出驱动模板,我也没有具体测试,等到实际用到是再研究吧,溜了溜了。


块设备驱动模板

struct xxx_dev {
    int size; 
    struct request_queue *queue;    /* The device request queue */
    struct gendisk *gd;             /* The gendisk structure */
    spinlock_t lock;                // 如果使用请求队列需要自旋锁
}

static int xxx_major;
module_param(xxx_major, int, 0);// 主设备号为0, 动态获取
#define HARDSECT_SIZE xxx       // HARDSECT_SIZE为硬盘的块大小, 一般为512字节
#define NSECTORS xxx            // 硬盘总的扇区数
#define xxx_MINORS              // 次设备号最大数目, 一个分区对应一个次设备号

/* 
 * 这里是具体的硬件操作
 * @sector: 要写/读硬盘的那个扇区
 * @nsect: 要写/读的扇区数目
 */
static void xxx_disk_transfer(struct vmem_disk_dev *dev, unsigned long sector,
        unsigned long nsect, char *buffer, int write)
{
    unsigned long offset = sector*KERNEL_SECTOR_SIZE;
    unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE;

    if ((offset + nbytes) > dev->size) {
        printk (KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes);
        return;
    }
    if (write)
        ...
    else
        ...
}

// 这个是通用的
static int gen_xfer_bio(struct xxx_dev *dev, struct bio *bio)
{
    struct bio_vec bvec;
    struct bvec_iter iter;
    sector_t sector = bio->bi_iter.bi_sector;   // 要写的扇区号
    // 遍历一个bio的所有bio_vec, __bio_kmap_atomic()将一段内存映射到
    // 要操作的内存页, 然后返回首地址. bio_cur_bytes(bio)获取当前bio_vec.bv_len
    bio_for_each_segment(bvec, bio, iter) {
        char *buffer = __bio_kmap_atomic(bio, iter);
        xxx_disk_transfer(dev, sector, bio_cur_bytes(bio) >> 9,
            buffer, bio_data_dir(bio) == WRITE);
        sector += bio_cur_bytes(bio) >> 9;
        __bio_kunmap_atomic(buffer);
    }
    return 0;
}

// 不使用请求队列绑定该函数
static void xxx_make_request(struct request_queue *q, struct bio *bio)
{
    struct xxx_dev *dev = q->queuedata;
    int status;

    status = gen_xfer_bio(dev, bio);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0)
        bio->bi_error = status;
        bio_endio(bio);
#else
        bio_endio(bio, status);
#endif
}

// 使用请求队列绑定该函数
static void vmem_disk_request(struct request_queue *q)
{
    struct request *req;
    struct bio *bio;
    // 1. 从请求队列中拿出一个请求
    while ((req = blk_peek_request(q)) != NULL) {
        struct xxx_dev *dev = req->rq_disk->private_data;
        if (req->cmd_type != REQ_TYPE_FS) {
            printk (KERN_NOTICE "Skip non-fs request\n");
            blk_start_request(req);
            __blk_end_request_all(req, -EIO);
            continue;
        }

        blk_start_request(req);
        // 2. 遍历请求中每一个bio
        __rq_for_each_bio(bio, req)
            gen_xfer_bio(dev, bio);
        __blk_end_request_all(req, 0);
    }
}


// 1. 申请主设备号
xxx_major = register_blkdev(xxx_major, "vmem_disk");

// 2. 记录硬盘大小
struct xxx_dev *dev = kzalloc(NSECTORS*sizeof(struct xxx_dev), GFP_KERNEL);
dev->size = NSECTORS*HARDSECT_SIZE;

// 3.1 不使用请求队列, 绑定xxx_make_request()函数
dev->queue = blk_alloc_queue(GFP_KERNEL);
blk_queue_make_request(dev->queue, xxx_make_request);

// 3.2 使用请求队列, 绑定xxx_request()函数
dev->queue = blk_init_queue(xxx_request, &dev->lock);

// 4. 设置请求队列的逻辑块大小, 将私有数据xxx绑定到队列中
blk_queue_logical_block_size(dev->queue, HARDSECT_SIZE);
dev->queue->queuedata = dev;

// 5. 申请gendisk(相当于cdev)并赋值, 然后添加到系统
dev->gd = alloc_disk(xxx_MINORS);

// 6. 给gendisk赋值, 然后添加到系统
dev->gd->major = xxx_major;
dev->gd->first_minor = 0;
dev->gd->fops = &xxx_ops;           // 绑定block_device_operations
dev->gd->queue = dev->queue;
dev->gd->private_data = dev;        // xxx绑定到了gendisk中
set_capacity(dev->gd, NSECTORS*(HARDSECT_SIZE/KERNEL_SECTOR_SIZE));
add_disk(dev->gd);

用户每进行一次对硬盘的操作, 都会被操作系统处理成一个请求, 然后放入相应的请求队列中(该请求队列由驱动定义), 一个请求包含若干个bio, 一个bio又包含若干个bio_vec

bio_vec指向用户需要写入硬盘的数据, 它由如下三个参数组成:

 struct bio_vec {
     struct page *bv_page;      // 数据所在页面的首地址
     unsigned int bv_len;       // 数据长度
     unsigned int bv_offset;    // 页面偏移量
 }

一个bio还包含一个bvec_iter, 它由如下4个参数组成:

 struct bvec_iter {
    sector_t        bi_sector;      // 要操作的扇区号
    unsigned int    bi_size;        // 剩余的bio_vec数目
    unsigned int    bi_idx;         // 当前的bio_vec的索引号
    unsigned int    bi_bvec_done;   // 当前bio_vec中已完成的字节数
};

通过bio, 再结合其中的bio_iter就可以找到当前的bio_vec.

用户可能发出若干对硬盘的操作, 也就对应着若干个bio, 操作系统能够按照一定的算法将这些操作重新组合成一个请求, 硬盘执行这个请求就能够以最高的效率将数据读取/写入.

以上操作仅适用于机械硬盘, 因为机械硬盘按照扇区顺序读写能够达到最高效率. 对于RAMDISK, ZRAM等可以随机访问的设备, 请求队列是没有必要的, 因此不需要请求队列.


网络设备驱动模板

static void xxx_rx (struct net_device *dev)
{
    struct xxx_priv *priv = netdev_priv(dev);
    struct sk_buff *skb;
    int length;

    length = get_rev_len(...); // 获取要接收数据的长度
    skb = dev_alloc_skb(length + 2);

    // 对齐
    skb_reserve(skb, 2);
    skb->dev = dev;

    // 硬件读取数据到skb
    ...

    // 获取上层协议类型
    skb->protocol = eth_type_trans(skb, dev);

    // 把数据交给上层
    netif_rx(skb);

    // 记录接收时间
    dev->last_rx = jiffies;
    ...
}

// 中断接收
static void xxx_interrupt(int irq, void *dev_id)
{
    struct net_device *dev = dev_id;
    status = ior(...); // 从硬件寄存器获取中断状态
    switch (status) {
    case IRQ_RECEIVER_ENENT:    // 接收中断
        xxx_rx(dev);
        break;
    ...
    }
}

static void xxx_timeout (struct net_device *dev)
{
    netif_stop_queue(dev);
    ...
    netif_wake_queue(dev);
}
// 数据发送
static xxx_start_xmit (struct sk_buf *skb, struct net_device *dev)
{
    int len;
    char *data, shortpkt[ETH_ZLEN];
    // 发送队列未满, 可以发送
    if (xxx_send_available()) {
        data = skb->data;
        len = skb->len;
        // 帧长度小于最小长度, 后面补0
        if (len < ETH_ZLEN) {
            memset(shortpkt, 0, ETH_ZLEN);
            memcpy(shortpkt, skb->data, skb->len);
            len = ETH_ZLEN;
            data = shortpkt;
        }
    }
    // 记录时间戳
    dev->trans_start = jiffies;

    if (...) {
        // 满足一定添加使用硬件发送数据
        xxx_hw_tx(data, len, dev);
    } else {
        // 否则停止队列
        netif_stop_queue(dev);
        ...
    }
}

static int xxx_open(struct net_device *dev)
{
    ...
    // 申请端口, IRQ等
    ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev);
    ...
    // 打开发送队列
    netif_start_queue(dev);
    ...
}

static const struct net_device_ops xxx_netdev_ops = {
    .ndo_open = xxx_open,
    .ndo_stop = xxx_stop,
    .ndo_start_xmit = xxx_start_xmit,
    .ndo_tx_timeout = xxx_timeout,
    .ndo_do_ioctl = xxx_ioctl,
    ...
}

// 1. 给net_device结构体分配内存, xxx_priv为私有数据
// 私有数据是和net_device绑定到一起的
struct net_device *ndev;
struct xxx_priv *priv;
ndev = alloc_etherdev(sizeof(struct xxx_priv));

// 2. 硬件初始化, 并将net_device_ops和ethtool_ops与ndev绑定
xxx_hw_init();
ndev->netdev_ops = &xxx_netdev_ops;
ndev->ethtool_ops = &xxx_ethtool_ops;
ndev->watchdog_timeo = timeout; 

// 3. 获取私有数据地址, 为私有数据赋值
priv = netdev_priv(ndev);
...

// 4. 注册ndev
register_netdev(ndev);

// 5. 注销ndev
unregister_netdev(ndev);
free_netdev(ndev);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux驱动开发04:块设备驱动和网络设备驱动 的相关文章

  • 内存分配函数

    1 malloc 函数 xff1a void malloc unsigned int size 在内存的动态分配区域中分配一个长度为size的连续空间 xff0c 如果分配成功 xff0c 则返回所分配内存空间的首地址 xff0c 否则返回
  • ORB_SLAM2+realsense运行稠密建图

    具体的环境及其细节 xff1a Ubuntu18 04 realsenseD435i ROS orbslam2 echo gou的博客 CSDN博客 下载代码 xff1a https github com gaoxiang12 ORBSLA
  • catkin_make学习总结

    catkin make学习总结 基础概念常用函数理解与注释其他有用的函数总结简单实例参考链接 基础概念 CMakeLists txt 文件中 xff0c 命令名字是不区分大小写的 xff0c 而参数和变量是大小写相关的ros的包 catki
  • 新手如何使用立创EDA完成电路设计

    软件简介 xff1a 立创EDA是一款基于浏览器的免费国产EDA绘图工具 下载方式 xff1a 百度 立创EDA 进入主页 xff0c 或主页点击 下载 客户端 xff0c 支持Wndows Luixus Mac系统下载安装 首先 xff0
  • Echart、Excel、highcharts、jfreechart对比

    Echart Excel highcharts jfreechart 柱状图 条形图 折线图 面积图 散点图 气泡图 K 线图 饼图 环形图 雷达图 力导布局图 和弦图 曲面图 地图
  • HAL库和标准库的区别

    本文回答来源于chat gpt4 xff0c 非原创 xff0c 也是我初学过程中所遇到的问题 xff0c 答案分享给大家 xff0c 如有侵权请联系删除 xff1a HAL 库 xff08 Hardware Abstraction Lay
  • 原来手机就能直接制作证件照,我也才知道,再也不用去照相馆了

    证件照选相信是我们大家日常所需 xff0c 但是去照相馆真的有点麻烦 xff0c 尤其是有时候仅仅只是需要换一个背景颜色 xff0c 其实不用这么麻烦 xff0c 现在手机上不仅能换背景颜色 xff0c 还能制作证件照 xff0c 还很简单
  • RTK基站 差分云共享技术,全套高精度定位解决方案

    针对区域内多个移动体高精度定位的需求 xff0c 为了最大程度的降低成本 xff0c FDISYSTEMS为DETA100系列具有联网功能的产品提供了免费的差分共享技术 xff0c 通过该技术可以将单一运载体从CORS服务器获取的差分修正R
  • STM32 Keil编程常见问题解决办法:(一)多行注释时出现红色下划线

    Problem xff1a 在STM32 Keil软件中进行多行注释时出现下图所示现象 xff0c 部分语句出现红色下划线 Solution xff1a 点击Keil软件上的小扳手 选择Text Completion 勾选ENTER TAB
  • 2017秋招求职历程总结

    2017秋招求职历程总结 从小的梦想就是有朝一日能够进入汽车行业工作 xff0c 很幸运刚毕业的第一份工作便实现了此梦想 xff0c 感谢大学遇到的那些人 终于在国庆之前拿到了一份还算满意的offer 9月1号从实习单位离职准备接下来的秋招
  • Win10常用命令:定时关机(shutdown命令)

    文章目录 一 单次 定时关机 xff1a Win 43 R 输入命令 xff1a 二 shutdown命令参数三 每天定时关机 一 单次 定时关机 xff1a Win 43 R 输入命令 xff1a 倒计时关机 xff1a shutdown
  • 如何实现超大文件(60G)传输给别人?

    2022 4 25 今天Ken问我要我工位上的一个虚拟机环境 xff0c 整个文件夹拷给他 但是这个CentOS的环境有60个G xff0c 我的U盘只有45G 想了几个办法 xff1a 压缩包 xff1a 用WinRAR压缩成压缩包 xf
  • CPU两大架构:X86与ARM的区别

    1 CPU 架构 Central Processing Unit Architecture X86 ARM MIPS PowerPC IA64 AMD64 x86 64 x64 是64位的CPU架构 区分ARM64 2 复杂指令集计算机CI
  • Linux(UOS、Ubuntu)虚拟机和Windows物理机之间无法复制粘贴

    我的UOS虚拟机和主机之间无法复制粘贴 xff0c 解决方案如下 xff1a 1 先更新一下软件列表 span class token function sudo span span class token function apt get
  • CMake、CMakeLists.txt

    2022 06 02 xff0c 今天开始研究cmake 不间断更新 一 说明 0 官方文档网址 xff1a www cmake org 1 cmake的定义 xff1a 高级编译配置工具 当多个人用不同的语言或者编译器开发一个项目 xff
  • ECMAScript6 入门 数组的扩展

    数组的扩展 1 xff1a 扩展运算符 xff1a 好比rest参数的逆运算 xff0c 将一个数组转换为用逗号分隔的参数序列 主要应用于函数调用 xff0c 将一个数组 xff0c 变为参数序列 如果扩展运算符后面是一个空的数组 xff0
  • CSDN排名记录

    文章目录 表格记录文字记录刷题记录 表格记录 时间 属性周排名总排名原创文章数收藏量粉丝数铁粉数2023年5月12日 xff1a 第20周3175593128893843912572023年5月6日 xff1a 第19周3211559128
  • 虚拟机使用的是此版本 VMware Workstation 不支持的硬件版本。 模块“Upgrade”启动失败。 未能启动虚拟机。

    问题 xff1a 虚拟机使用的是此版本 VMware Workstation 不支持的硬件版本 模块 Upgrade 启动失败 未能启动虚拟机 分析 xff1a 该虚拟机环境之前使用的VMware版本与你所使用的VMware版本不一致 大概
  • C++基础

    文章目录 推荐速成视频一 数据的输入输出cin xff1a 输入cin getline xff1a 读取一行内容cout xff1a 输出I O格式控制 二 C 43 43 函数重载三 类和对象1 struct与class 1 struct
  • Ch1. 逻辑结构、存储结构、时间复杂度、空间复杂度

    文章目录 一 逻辑结构 与 存储结构 1 逻辑结构 1 集合结构 2 线性结构 3 树形结构 4 图形结构 2 存储结构 物理结构 1 顺序存储 2 链式存储 3 索引存储 4 散列存储 概念 二 时间复杂度 空间复杂度

随机推荐