字符型设备驱动程序--gpio 驱动实例

2023-05-16


概述: 字符设备驱动程序: 是按照字符设备要求完成的由操作系统调用的代码。
重点理解以下内容:
 1. 驱动是写给操作系统的代码,它不是直接给用户层程序调用的,而是给系统调用的
 2. 所以驱动要向系统注册。
 3. 注册的时候,要求驱动必须符合一定的规范,否则系统就会不认识。这就是程序架构。
 4. 字符设备驱动对应一个cdev 结构, 需要向系统注册或申请设备号,注册cdev设备,
    完成cdev 设备需要的操作,诸如读,写,ioctl操作等。
 5. 系统下驱动以模块的形式而存在
 6. 用户空间验证,需要先建立设备节点
    例如 mknod /dev/gpio c 126 0  将创建/dev/gpio 节点, 主设备号126,从设备号0
    然后用 echo 'a' >/dev/gpio 查看写入
           cat /dev/gpio       查看读出。

    你也可以书些标准的文件访问来测试 /dev/gpio, 这里从略。

这个126 如果是系统申请的,则是动态的,你需要用cat /proc/devices 去查询系统给你的驱动分配了什么设备号。

然后再创建设备结点。

如果启用了sysfs, 则在 /sys/module/gpio 目录下有相应的属性信息描述。

补充:

1。可以用cat /proc/devices | grep <设备名> 查看系统分配(或自己指定)的主设备号。

2. 当mknod 以后,可以用 ll /dev | grep <设备名> 查看设备的主设备号,从设备号

--------------------------------------------------------------------------------
 下面给出一个实例:
 gpioadaptor.c 演示字符设备向系统注册的情景。
 gpio.c 是真正的硬件驱动代码。(这里只是用printk 打印了相关信息)
 在centos 3.10 内核上测试通过
--------------------------------------------------------------------------------

/*======================================================================
  A gpio driver as an example of char device drivers  
  author: hjjdebug
  date: Fri May  9 17:54:33 CST 2014

  ======================================================================*/

// gpioadaptor.c

#include <linux/module.h>        // module 架构及宏定义
// struct cdev 定义, 字符型设备结构体。标准化结构
#include <linux/cdev.h>            
// struct file 定义, 设备操作是按文件来操作的,所以用到文件指针
#include <linux/fs.h>            
#include <linux/slab.h>            // kmalloc, kfree 声明, 为设备变量分配缓存
#include <asm/uaccess.h>        // copy_to_user , copy_from_user 声明, 数据copy
#include "gpio.h"

#define GPIO_SIZE    0x4            // 4字节做为gpio 缓存, 你可以定义的更大一些。
#define GPIO_MAJOR 254            /*预设的gpio的主设备号*/

// IOCTL 命令定义
#define ALL_MEM_CLEAR 0x1  /*清0全部内存*/
#define SET_MEM_ADDR  0x2   // 设置操作的gpio 地址
#define WRITE_DATA      0x3   // 写io 端口
#define READ_DATA          0x4   // 读io 端口

/*gpio设备结构体, 定义自己使用的变量,并要包含一个cdev 成员,与系统字符设备接口*/
struct gpio_dev                                     
{                                                        
    struct cdev cdev; /*cdev结构体*/                       
    unsigned char mem[GPIO_SIZE]; /*全局内存*/        
    int addr;        // gpio 地址, 操作哪一个gpio
};

// 全局变量定义
static int gpio_major = GPIO_MAJOR;  // 保留申请的主设备号
struct gpio_dev *gpio_devp; /*设备结构体指针*/
/*文件打开函数, 将gpio_devp 传递给file 结构的私有数据*/
int gpio_open(struct inode *inode, struct file *filp)
{
    /*将设备结构体指针赋值给文件私有数据指针*/
    filp->private_data = gpio_devp;
    return 0;
}
/*文件释放函数*/
int gpio_release(struct inode *inode, struct file *filp)
{
    return 0;
}

// ioctl设备控制函数
// 对于简单的gpio. 也许ioctl就已经足够了,而不许要read,write 接口了。
// 这里为了完整,仍然写了read, write,等,完成批量内存操作
long gpio_ioctl(struct file *filp, unsigned
        int cmd, unsigned long arg)
{
    struct gpio_dev *pDev = filp->private_data;/*获得设备结构体指针*/

    switch (cmd)
    {
        case ALL_MEM_CLEAR:
            memset(pDev->mem, 0, GPIO_SIZE);      
            printk(KERN_INFO "all gpio is set to zero\n");
            break;
        case SET_MEM_ADDR:
            pDev->addr = arg;
            printk(KERN_INFO "addr is %d\n",pDev->addr);
            break;
        case WRITE_DATA:
            pDev->mem[pDev->addr]=arg;
            GPIOSetData(pDev->addr, arg);
            printk(KERN_INFO "Data Write: %d\n",(int)arg);
            break;
        case READ_DATA:
            arg=pDev->mem[pDev->addr];
            printk(KERN_INFO "Data Read: %d\n",(int)arg);
            break;


        default:
            return  - EINVAL;
    }
    return 0;
}

//读函数, 可以一次读多个gpio 的数值,似乎有些多余,但体现read 的能力
static ssize_t gpio_read(struct file *filp, char __user *buf, size_t size,
        loff_t *ppos)
{
    unsigned long offset =  *ppos;
    unsigned int count = size;
    int ret = 0;
    struct gpio_dev *pDev = filp->private_data; /*获得设备结构体指针*/

    printk("need size:%ld, offset:%ld\n",size,offset);

    /*分析和获取有效的写长度*/
    if (offset > GPIO_SIZE)
    {
        return count ?  - ENXIO: 0;
    }
    else if(offset == GPIO_SIZE)
    {
        return 0;   // 防止测试cat /dev/gpio 时 文件尾出现错误提示
    }
    if (count > GPIO_SIZE - offset)
    {
        count = GPIO_SIZE - offset;
    }

    /*内核空间->用户空间*/
    if (!copy_to_user(buf, (void*)(pDev->mem + offset), count))
    {
        *ppos += count;
        printk(KERN_INFO "read %d bytes(s) from %ld addr\n", count, offset);
        ret = count;
    }
    else
    {
        ret =  - EFAULT;
    }

    return ret;
}

/*写函数*/
static ssize_t gpio_write(struct file *filp, const char __user *buf,
        size_t size, loff_t *ppos)
{
    unsigned long offset =  *ppos;
    unsigned int count = size;
    int ret = 0;
    int i;
    struct gpio_dev *pDev = filp->private_data; /*获得设备结构体指针*/

    /*分析和获取有效的写长度*/
    if (offset >= GPIO_SIZE)
    {
        return count ?  - ENXIO: 0;
    }
    if (count > GPIO_SIZE - offset)
    {
        count = GPIO_SIZE - offset;
    }

    /*用户空间->内核空间*/
    if (!copy_from_user(pDev->mem + offset, buf, count))
    {
        *ppos += count;
        for(i=0; i< count; i++)
        {
            GPIOSetData(offset+i, pDev->mem[offset+i]);
        }    
        printk(KERN_INFO "written %d bytes(s) to %ld addr\n", count, offset);
        ret = count;
    }
    else
    {
        ret =  - EFAULT;
    }

    return ret;
}

/* seek文件定位函数 */
static loff_t gpio_llseek(struct file *filp, loff_t offset, int orig)
{
    loff_t ret = 0;
    switch (orig)
    {
        case 0:   /*相对文件开始位置偏移*/
            if (offset < 0)
            {
                ret =  - EINVAL;
                break;
            }
            if ((unsigned int)offset > GPIO_SIZE)
            {
                ret =  - EINVAL;
                break;
            }
            filp->f_pos = (unsigned int)offset;
            ret = filp->f_pos;
            break;
        case 1:   /*相对文件当前位置偏移*/
            if ((filp->f_pos + offset) > GPIO_SIZE)
            {
                ret =  - EINVAL;
                break;
            }
            if ((filp->f_pos + offset) < 0)
            {
                ret =  - EINVAL;
                break;
            }
            filp->f_pos += offset;
            ret = filp->f_pos;
            break;
        default:
            ret =  - EINVAL;
            break;
    }
    return ret;
}

/*文件操作结构体*/
static const struct file_operations gpio_fops =
{
    .owner = THIS_MODULE,
    .llseek = gpio_llseek,
    .read = gpio_read,
    .write = gpio_write,
    .compat_ioctl = gpio_ioctl,
    .open = gpio_open,
    .release = gpio_release,
};

/*向系统注册设备*/
static void gpio_setup_cdev(struct gpio_dev *pDev, int index)
{
    int err, devno = MKDEV(gpio_major, index);

    cdev_init(&pDev->cdev, &gpio_fops);
    pDev->cdev.owner = THIS_MODULE;
    pDev->cdev.ops = &gpio_fops;
    err = cdev_add(&pDev->cdev, devno, 1);
    if (err)
        printk(KERN_NOTICE "Error %d adding CDEV%d", err, index);
}

/*模块加载函数*/
int gpio_init(void)
{
    int result = -1;
    dev_t devno = MKDEV(gpio_major, 0);

    /* 申请设备号*/
    if (gpio_major)
    {
        result = register_chrdev_region(devno, 1, "gpio");
    }
    if (result < 0) // 设备号已被占用等
    {
        /* 动态申请设备号 */
        result = alloc_chrdev_region(&devno, 0, 1, "gpio");
        gpio_major = MAJOR(devno);
    }  
    if (result < 0)
    {
        printk("gpio module register devno failed!, result:%d\n",result);
        return result;
    }

    /* 动态申请设备结构体的内存*/
    gpio_devp = kmalloc(sizeof(struct gpio_dev), GFP_KERNEL);
    if (!gpio_devp)    /*申请失败*/
    {
        result =  - ENOMEM;
        goto fail_malloc;
    }
    memset(gpio_devp, 0, sizeof(struct gpio_dev));

    gpio_setup_cdev(gpio_devp, 0);
    // 调用硬件层初始化
    GPIOInit(NULL, GPIO_SIZE);
    printk("gpio module installed!\n");
    return 0;

fail_malloc: unregister_chrdev_region(devno, 1);
             return result;
}

/*模块卸载函数*/
void gpio_exit(void)
{
    if(gpio_devp)
    {
        cdev_del(&gpio_devp->cdev);   /*注销cdev*/
        kfree(gpio_devp);     /*释放设备结构体内存*/
        unregister_chrdev_region(MKDEV(gpio_major, 0), 1); /*释放设备号*/
    }
    gpio_devp = 0;
    printk(KERN_INFO "gpio module released!\n");
}


module_init(gpio_init);
module_exit(gpio_exit);

MODULE_AUTHOR("HJJDEBUG");
MODULE_LICENSE("GPL");
--------------------------------------------------------------------------------
// 真正的驱动,gpio.c  (虚拟)
#include <linux/kernel.h>
#include "gpio.h"
/***************************************************
 * 这里是个虚拟的驱动, 所有的硬件寄存器操作全部忽略。
 ***************************************************/
int GPIOInit(int *pAddr, int size)
{
    printk("all gpio has inited ok!\n");
    return 0;
}
int GPIOSetData(int addr, int data)
{
    printk("gpio addr:%d, data:%d\n", addr, data);
    return 0;
}

--------------------------------------------------------------------------------

//补充gpio.h

 $ cat gpio.h
#ifndef _GPIO_H
#define _GPIO_H
int GPIOInit(int *pAddr, int size);
int GPIOSetData(int addr, int data);
#endif


--------------------------------------------------------------------------------

Makefile:

ifneq ($(KERNELRELEASE),)

#    obj-m := test.o
    obj-m := m_gpio.o
    m_gpio-y:= gpioadaptor.o gpio.o

else
    PWD=$(shell pwd)
    KVER=$(shell uname -r)
    KDIR=/lib/modules/$(KVER)/build
all:
    make -C $(KDIR) M=$(PWD)
clean:
    rm *.o *.ko modules.* Module.symvers *.mod.c
endif
 

--------------------------------------------------------------------------------

补充一个字符设备测试程序,注意open 的模式, 如果写成0(RD_ONLY), 写两个字符会出错。

错误号为9, Bad file descriptor. 但写一个字符还是可以的
[root@hjj]# cat test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

char *data="ab";
int main(int argc, char *argv[])
{
    int fd = open("/dev/udp",O_WRONLY); // 注意文件打开模式
    if(fd== -1)
    {
        printf("error open device.\n");
        exit(1);
    }
    printf("fd:%d\n",fd);
    ssize_t size=write(fd,data,strlen(data));
    if(size==-1)
    {
        printf("errno:%d string:%s\n",errno,strerror(errno));
        perror("reason:");
    }
    else
    {
        printf("size:%ld bytes write\n",size);
    }
    close(fd);
    return 0;

}

一个主设备号可以对应多个从设备号,每一个主设备号从设备号对应一个设备文件,打开不同的设备文件,会返回不同的fd, 不同的fd, 会对应不同的filp.从而可以存储各自的数据

这样依据fd, 就可以操作不同的数据。

补充:

也可以自动创建创建设备节点,省去了手工创建设备节点的过程,具体代码请搜索互联网

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

字符型设备驱动程序--gpio 驱动实例 的相关文章

  • 死锁面试题(什么是死锁,产生死锁的原因及必要条件)

    什么是死锁 xff1f 所谓死锁 xff0c 是指多个进程在运行过程中因争夺资源而造成的一种僵局 xff0c 当进程处于这种僵持状态时 xff0c 若无外力作用 xff0c 它们都将无法再向前推进 因此我们举个例子来描述 xff0c 如果此
  • 标准模板库-容器

    标准模板库STL Standard Template Libarary 是一个标准类与函数模板的库 STL包含容器 容器适配器 迭代器 算法 函数对象和函数适配器 容器 用来存储和组织其他对象的对象 T是存储在容器中的元素类型的模板类型形参
  • Ubuntu18.04安装PX4踩坑、报错及解决方案整理

    笔者最近需要跑无人机巡检大坝的仿真 xff0c 于是在自己的Ubuntu2018 04中开始安装PX4 xff0c 问过不少之前已经装过PX4的师兄和同学 xff0c 都曾在PX4安装过程中踩过许多坑 xff0c 耗费了不少时间 xff0c
  • 初识ROS文件结构:以阿木实验室Prometheus项目为例

    ROS的工作空间是一个存放工程开发相关文件的文件夹 xff0c Fuerte版本之后的ROS默认使用的是Catkin编译系统 功能包是ROS软件中的基本单元 xff0c 包含ROS节点 库 配置文件等 一个文件夹是功能包的标志是该文件夹中有
  • 六轴无人机装配问题小结(Pixhawk飞控、APM固件、电机装配、电调校准)

    笔者近期需要组装一架六轴无人机供超声波避障模块 单点激光雷达等传感器的测试使用 由于是第一次碰真机 xff0c 面对散落一箱的部件还是非常的头大的 xff0c 不过好在实验室有经验的大佬能提供一些指导 xff0c 并且还能够参考 创客智造
  • 测试API接口,返回404。

    报错 xff1a 34 timestamp 34 34 2020 06 02T12 40 53 125 43 00 00 34 34 status 34 404 34 error 34 34 Not Found 34 34 message
  • 再谈STM32的CAN过滤器-bxCAN的过滤器的4种工作模式以及使用方法总结

    转自 xff1a http blog csdn net flydream0 article details 52317532 1 前言 bxCAN是STM32系列最稳定的IP核之一 xff0c 无论有哪个新型号出来 xff0c 这个IP核基
  • NVIDIA TX2自制底板的USB口无法使用的一种解决方法

    这是由于官方的底板上采用INA3221芯片做了电源监控电路 xff0c 只有确保5V电源达到要求的情况下才会使能USB口 而自制的底板上将上述电路省略了 xff0c 所以导致了USB口无法使用 解决办法就是要给TX2更新设备树 在网上找到一
  • Benchmark(基准测试)初相识

    一 benchmark概念 在计算中 xff0c 基准是运行一个计算机程序 一组程序或其他操作的行为 xff0c 以评估一个对象的相对性能 xff0c 通常是通过对它运行一些标准测试和试验 基准测试一词也通常用于精心设计的基准测试程序本身
  • 嵌入式中的通讯协议——UART、I2C、SPI、DMA

    目录 一 通讯的基本概念 二 USART 串口通讯 三 I2C通讯协议 四 SPI通讯协议 五 DMA 直接存储器存取 六 USART I2C SPI比较 一 通讯的基本概念 1 串行通讯与并行通讯 xff08 按数据的传送方式 xff09
  • RISC与CISC比较

    RISC的设计重点在于降低由硬件执行指令的复杂度 xff0c 因为软件比硬件容易提供更大的灵活性和更高的智能 xff0c 因此RISC设计对编译器有更高的要求 xff1b CISC的设计则更侧重于硬件执行指令的功能 xff0c 使CISC的
  • 操作系统选择调度方式和算法的若干准则

    1 调度的类型 按调度的层次 xff1a 长期 xff08 长程 作业 高级 xff09 调度 xff1b 中期 xff08 中级 中程 xff09 调度 xff1b 短期 xff08 短程 进程 低级 xff09 调度 按OS 的类型 x
  • 提灯过桥问题

    题目 xff1a 小明一家过一座桥 xff0c 过桥时是黑夜 xff0c 所以必须有灯 现在小明过桥要1秒 xff0c 小明的弟弟要3秒 xff0c 小明的爸爸要6秒 xff0c 小明的妈妈要8秒 xff0c 小明的爷爷要12秒 每次此桥最
  • 如何判断一个整数数组中是否有重复元素

    题目 xff1a 写一个函数判断一个int类型的数组是否是有效的 所谓有效是指 xff1a 假设数组大小为n xff0c 那么这个int数组里的值为0 n 1之间的数 xff0c 并且每个数只能出现一次 xff0c 否则就是无效数组 例如
  • C++发送HTTP请求---亲测可行

    转自 xff1a http hi baidu com benbearlove item 1671c23017575825b3c0c53f 环境 xp sp3 vs2008 vs2010在静态库中使用 MFC include lt afxwi
  • 百度2014开发测试工程师笔试题(沈阳站)

    时间 xff1a 2013 9 21 地点 xff1a 沈阳 职位 xff1a 开发测试工程师
  • 2014百度校招开发测试工程师笔试题

    时间 xff1a 2013 9 28 地点 xff1a 深圳 职位 xff1a 开发测试工程师
  • 整体了解HADOOP框架及一些开源项目

    Hadoop框架中 xff0c 有很多优秀的工具 xff0c 帮助我们解决工作中的问题 Hadoop的位置 从上图可以看出 xff0c 越往右 xff0c 实时性越高 xff0c 越往上 xff0c 涉及到算法等越多 越往上 xff0c 越
  • Kafka简介

    Kafka简介 在当前的大数据时代 xff0c 第一个挑战是海量数据的收集 xff0c 另一个就是这些数据的分析 数据分析的类型通常有用户行为数据 应用性能跟踪数据 活动数据日志 事件消息等 消息发布机制用于连接各种应用并在它们之间路由消息
  • Flume入门笔记------架构以及应用介绍

    在具体介绍本文内容之前 xff0c 先给大家看一下Hadoop业务的整体开发流程 xff1a 从Hadoop的业务开发流程图中可以看出 xff0c 在大数据的业务处理过程中 xff0c 对于数据的采集是十分重要的一步 xff0c 也是不可避

随机推荐

  • 分布式服务框架dubbo原理解析

    alibaba有好几个分布式框架 xff0c 主要有 xff1a 进行远程调用 类似于RMI的这种远程调用 的 dubbo hsf xff0c jms消息服务 napoli notify xff0c KV数据库 tair 等 这个框架 工具
  • Linux下安装ElasticSearch

    Linux下安装ElasticSearch 一 下载 amp 安装二 安装中遇到的问题及解决方案三 使用中遇到的问题及解决方案四 安装head五 安装kibana 一 下载 amp 安装 先安装JDK 下载elasticsearch 7 0
  • cmake学习1:基本的CMakeLists的编写

    前言 自己在使用cmake进行编译工程的时候不太了解cmake的基本使用方法 有时候出现找不到第三方库的问题也不知如何排查 因此相对cmake有个稍微系统的认识 希望能用这个强大的工具来更好的为自己的工程服务 因此总结为了几篇博客 主要参考
  • ZooKeeper 报错 ERROR [main:ZooKeeperServerMain@64] 的解决办法

    myid span class hljs type ERROR span main span class hljs type ZooKeeperServerMain span 64 span class hljs number 64 spa
  • Ubuntu14.04下安装cmake 3.9.6

    简述 xff1a CMake是一个跨平台 的编译自动配置 工具 xff0c 它使用一个名为CMakeLists txt 的文件来描述构建过程 xff0c 可以产生标准的构建文件 它可以用简单的语句来描述所有平台的安装 编译过程 它能够输出各
  • 二维数组功能测试,超详细

    include lt stdio h gt int main char buf 2 5 61 39 a 39 39 b 39 39 c 39 39 d 39 39 e 39 39 f 39 39 g 39 39 h 39 39 i 39 3
  • Cmake知识----编写CMakeLists.txt文件编译C/C++程序

    简述 xff1a CMake是一个跨平台的安装 编译 工具 可以用简单的语句来描述所有平台的安装 编译过程 他能够输出各种各样的makefile或者project文件 能测试编译器所支持的C 43 43 特性 类似UNIX下的automak
  • 知识点总结:Java核心技术(卷1)

    Java核心技术 xff08 卷1 xff09 一 基础概念 1 1 基本程序设计结构 1 1 数据类型 1 1 1 数值类型 1 从java7开始 xff0c 加上前缀0b或0B就可以写二进制 xff1b 2 指数的表示 十进制中以10为
  • 单片机-控制-直流电机-基于L9110S-、L298N、TB6612FNG驱动

    直流电机 xff08 direct current machine xff09 能将直流电能转换成机械能 xff08 直流电动机 xff09 或将机械能转换成直流电能 xff08 直流发电机 xff09 的旋转电机 它是能实现直流电能和机械
  • 【一文弄懂】张正友标定法-完整学习笔记-从原理到实战

    张正友标定法 完整学习笔记 从原理到实战 文章目录 张正友标定法 完整学习笔记 从原理到实战 xff08 零 xff09 前言 xff1a 1 为什么需要标定 xff1f 2 相机标定的已知条件和待求解是什么 xff1f 标定前的已知条件
  • realsense ——SR300 相机使用小记

    环境搭建相关的参考资料挺多的 xff0c 这里就不多说了 这里记一些相关的api 算了 xff0c 还是给出自己的配置记录吧https blog csdn net hehehetanchaow article details 1057958
  • kubeadm安装部署Kubernetes(k8s)与部署 Dashboard web_UI(附Kubernetes安装脚本)

    前言 Kubernetes作为容器编排工具 xff0c 简化容器管理 xff0c 提升工作效率而颇受青睐 很多新手部署Kubernetes由于 科学上网 问题举步维艰 xff0c 本文以实战经验详解kubeadm不用 科学上网 部署Kube
  • 开源飞控资料

    四轴开源资料 http merafour blog 163 com blog m 61 0 amp t 61 1 amp c 61 fks 08406908208008307008408308409508608708806608408408
  • 深度学习视频数据集(动作识别):UCF-101

    UCF 101 官网 xff1a https www crcv ucf edu research data sets ucf101 网盘 xff1a 链接 xff1a https pan baidu com s 1RsJuykWyUlQ4
  • 视频分析模型(行为识别):C3D

    C3D 文章目录 C3D1 简介1 1 背景1 2 C3D特点1 3 视频描述符1 4 C3D的结果 2 架构2 1 工作流程2 2 网络结构2 3 3D卷积和池化2 4 kernel 的时间深度 3 可视化3 1 特征图3 2 特征嵌入
  • OpenCV下车牌定位算法实现代码 (二)

    前面介绍了用OpenCV的squares实例定位车牌的算法 xff0c 效果不是很理想 车牌定位的方法有很多种 xff0c 这里我们从汽车图像的纹理特征入手 xff0c 找出车牌不同于背景的特征是车牌定位的关键 观察多幅汽车图片我们会发现车
  • 旷视张祥雨:神经网络架构设计新思路

    点击上方 CVer xff0c 选择加 34 星标 34 置顶 重磅干货 xff0c 第一时间送达 本文转载自 xff1a AI科技评论 作者 蒋宝尚 编辑 青暮 深度学习模型在很多任务上都取得了不错的效果 xff0c 但调参却是一项非常痛
  • 发现三本不错的讲解数据存储的书

    研究数据存储 xff0c 没有很多现成的东西 xff0c 但是可以参考数据库系统的存储实现的内容 xff0c 发现三本书 xff0c 觉得值得一读 数据库系统全书 http www china pub com computers commo
  • Failed to get convolution algorithm. This is probably because cuDNN failed to initialize, 4

    处理Failed to get convolution algorithm This is probably because cuDNN failed to initialize so try looking to see if a war
  • 字符型设备驱动程序--gpio 驱动实例

    概述 xff1a 字符设备驱动程序 xff1a 是按照字符设备要求完成的由操作系统调用的代码 重点理解以下内容 1 驱动是写给操作系统的代码 xff0c 它不是直接给用户层程序调用的 xff0c 而是给系统调用的 2 所以驱动要向系统注册