• Linux第70步_新字符设备驱动的一般模板


    1、了解“申请和释放设备号函数”

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

    //注册字符设备驱动

    //dev:保存申请到的设备号

    //baseminor:次设备号的起始地址

    //count:要申请的设备数量;

    // name:表示“设备名字”

    注意:

    没有指定主设备号,但是给了“次设备号的基地址”和“次设备的数量”,可以使用alloc_chrdev_region()注册设备号;

    int register_chrdev_region(dev_t from, unsigned count, const char *name)

    from表示起始设备号

    count表示次设备号的数量

    name表示设备名

    注意:

    指定“起始设备号”和“次设备号的数量”,可以使用register_chrdev_region()注册设备号;

    void unregister_chrdev_region(dev_t from, unsigned count)

    //释放字符设备号

    from表示起始设备号

    count表示次设备的数量

    注意:

    指定“起始设备号”和“次设备的数量”,可以使用unregister_chrdev_region()注销设备号;

    2、申请和释放设备应用举例:

    int major; /* 主设备号 */

    int minor; /* 次设备号 */

    dev_t devid; /* 设备号 */

    if (major)/* 定义了主设备号 */

    {

    devid = MKDEV(major, 0);

    //major左移20位,再与0相或,就得到“Linux设备号”

    //输入参数major为“主设备号”

    //输入参数0为“次设备号”,大部分驱动次设备号都选择0

    register_chrdev_region(devid, 1, "DevicName");

    //注册设备号

    //devid表示起始设备号

    //1表示次设备号的数量

    //DevicName表示设备名

    }

    else

    { /* 没有定义设备号 */

    alloc_chrdev_region(&devid, 0, 1, "DevicName");

    //注册字符设备驱动

    //devid:保存申请到的设备号

    //0:次设备号的起始地址

    //1:要申请的设备数量;

    // DevicName:表示“设备名字”

    major = MAJOR(devid); /* 获取分配号的主设备号 */

    //输入参数devid为“Linux设备号”

    //devid右移20位得到“主设备号”

    minor = MINOR(devid); /* 获取分配号的次设备号 */

    //输入参数devid为“Linux设备号”

    //devid与0xFFFFF相与后得到“次设备号”

    }

    unregister_chrdev_region(devid, 1);

    /* 释放设备号 */

    //devid:需要释放的设备号

    //1:需要释放的次设备号数量;

    4、了解“字符设备结构”,初始化字符设备,添加和删除字符设备的函数

    “字符设备结构类型cdev”,位于在include/linux/cdev.h文件中,如下:

    struct cdev {

      struct kobject kobj;

      struct module *owner;//使用THIS_MODULE将owner指针指向当前这个模块

      const struct file_operations *ops;//字符设备文件操作函数集合

      struct list_head list;

      dev_t dev;         //32位设备号

      unsigned int count;//次设备号数量

    } __randomize_layout;

    在include/linux/types.h文件中,可以查到如下:

    typedef u32 __kernel_dev_t

    //为“u32”起个别名叫“__kernel_dev_t”

    typedef __kernel_dev_t dev_t;

    //为“__kernel_dev_t”起个别名叫“dev_t”

    void cdev_init(struct cdev *cdev, const struct file_operations *fops);

    //初始化字符设备

    //cdev是等待初始化的结构体变量

    // fops就是字符设备文件操作函数集合

    int cdev_add(struct cdev *p, dev_t dev, unsigned count);

    //添加字符设备

    // p表示指向要添加的字符设备,即字符设备结构cdev变量

    // dev表示设备号

    // count表示需要添加的设备数量

    void cdev_del(struct cdev *p);

    //删除字符设备

    //p表示指向需要删除的字符设备,即字符设备结构cdev变量

    5、初始化字符设备,添加和删除字符设备应用举例:

    dev_t devid; /*声明32位变量devid用来给保存设备号 */

    const struct file_operations  test_fops = {

      .owner = THIS_MODULE,

      .open = CharDeviceXXX_open,

      .read = CharDeviceXXX_read,

      .write = CharDeviceXXX_write,

      .release = CharDeviceXXX_release,

    };

    struct  cdev  test_cdev;//声明cdev字符设备结构变量test_cdev

    Test_cdev.owner = THIS_MODULE;

    //使用THIS_MODULE将owner指针指向当前这个模块

    cdev_init(&test_cdev,& test_fops);

    //初始化字符设备结构变量test_cdev”

    //test_cdev是等待初始化的结构体变量

    // test_fops就是字符设备文件操作函数集合

    cdev_add(&testcdev, devid, 1);

    //添加字符设备

    // &testcdev表示指向要添加的字符设备,即字符设备结构testcdev变量

    // devid表示设备号

    // 1表示需要添加的设备数量

    cdev_del(&testc_dev);

    //删除字符设备

    //&testc_dev表示指向需要删除的字符设备,即字符设备结构testc_dev变量

    6、节点文件的自动创建与删除

    设备文件的自动创建与删除是通过mdev用户程序来实现的。

     struct class *class_create (struct module *owner, const char *name);

    //创建类

    //owner一般为THIS_MODULE

    //参数name是类名字

    //返回值是指向结构体class的指针,也就是创建的类

    void class_destroy(struct class *cls);

    //删除类

    //参数cls就是要删除的类

    struct device *device_create(struct class *cls,struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)

    //device_create是个可变参数的函数,用来创建设备

    //参数cls就是设备要创建在哪个类下面

    //参数parent是父设备,一般为 NULL,也就是没有父设备

    //参数devt是设备号;

    //参数drvdata 是设备可能会使用的一些数据,一般为 NULL;

    //参数fmt是设备名字

    //如果设置fmt=xxx 的话,就会生成/dev/xxx设备文件。

    //返回值就是创建好的设备。

    void device_destroy(struct class *cls, dev_t devt);

    //删除创建的设备

    //参数classs是要删除的设备所处的类

    //参数devt是要删除的设备号

    7、创建设备和删除设备举例:

    struct class *class; /* 类 */

    struct device *device; /* 设备 */

    dev_t devid; /* 设备号 */

    /* 驱动入口函数 */

    static int __init xxx_init(void)

    {

    /* 创建类 */

    class = class_create(THIS_MODULE, "Class_Name");

    //创建类

    //使用THIS_MODULE将owner指针指向当前这个模块

    //Class_Name是类名字

    //返回值是指向结构体class的指针,也就是创建的类

    /* 创建设备 */

    device = device_create(class, NULL, devid, NULL, "Class_Name");

    //创建设备

    //设备要创建在class类下面

    //NULL表示没有父设备

    //devid是设备号;

    //参数drvdata=NULL,设备没有使用数据

    //Class_Name是设备名字

    //如果设置fmt=xxx 的话,就会生成/dev/xxx设备文件。

    //返回值就是创建好的设备。

    return 0;

    }

    /* 驱动出口函数 */

    static void __exit led_exit(void)

    {

    /* 删除设备 */

    device_destroy(newchrled.class, newchrled.devid);

    //删除创建的设备

    //newchrled.class是要删除的设备所处的类

    //newchrled.devid是要删除的设备号

    /* 删除类 */

    class_destroy(newchrled.class);

    //删除类

    //newchrled.class就是要删除的类

    }

    module_init(led_init);

    module_exit(led_exit);

    6、创建NewCharDeviceXXX目录

    输入“cd /home/zgq/linux/Linux_Drivers/回车

    切换到“/home/zgq/linux/Linux_Drivers/”目录

    输入“ls回车”查看“/home/zgq/linux/Linux_Drivers/”目录的文件和文件夹

    输入“mkdir NewCharDeviceXXX回车”,创建NewCharDeviceXXX目录

    输入“cp CharDeviceXXX_1/* NewCharDeviceXXX/回车

    将“CharDeviceXXX_1/”目录下的所有文件拷贝到“NewCharDeviceXXX/”目录下

    输入“cd NewCharDeviceXXX/回车

    切换到“/home/zgq/linux/Linux_Drivers/NewCharDeviceXXX/”目录

    输入“ls回车”查看“/home/zgq/linux/Linux_Drivers/NewCharDeviceXXX/”目录的文件和文件夹

    输入“mv CharDeviceXXX.c NewCharDeviceXXX.c回车

    将“CharDeviceXXX.c”更名为“NewCharDeviceXXX.c”

    输入“mv CharDeviceXXX_APP.c NewCharDeviceXXX_APP.c回车

    将“CharDeviceXXX_APP.c”更名为“NewCharDeviceXXX_APP.c

    输入“ls回车”查看“/home/zgq/linux/Linux_Drivers/NewCharDeviceXXX/”目录的文件和文件夹

    7、修改Makefile文件

    打开虚拟机上“VSCode”,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“Linux_Drivers”,点击“NewCharDeviceXXX”。

    修改后Makefile文件如下:

    KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31

    #使用“:=”将其后面的字符串赋值给KERNELDIR

    CURRENT_PATH := $(shell pwd)

    #采用“shell pwd”获取当前打开的路径

    #使用“$(变量名)”引用“变量的值”

    obj-m := NewCharDeviceXXX.o

    #给“obj-m”赋值为“NewCharDeviceXXX.o”

    drv: kernel_modules

    #生成“drv”需要依赖“kernel_modules”

        @echo $(KERNELDIR)

    #输出KERNELDIR的值为“/home/zgq/linux/atk-mp1/linux/linux-5.4.31”

        @echo $(CURRENT_PATH)

    #输出CURRENT_PATH的值为/home/zgq/linux/Linux_Drivers/NewCharDeviceXXX”

        @echo $(MAKE)

    #输出MAKE的值为make

    kernel_modules:

        $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

    #后面的"modules"表示编译成模块

    #“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”

    #“CURRENT_PATH”上面定义为“当前的工作目录”

    #“-C $(KERNELDIR) M=$(CURRENT_PATH) ”表示将“当前的工作目录”切换到“指定的目录”中

    #即切换到“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”。

    #M表示模块源码目录

    #在“make和modules”之间加入“M=$(CURRENT_PATH)”,表示切换到由“CURRENT_PATH”指定的目录中读取源码,同时将其编>译为.ko 文件

    clean_drv:

        $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

    #“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”

    #“CURRENT_PATH”上面定义为“当前的工作目录

    app:

        arm-none-linux-gnueabihf-gcc NewCharDeviceXXX_APP.c -o NewCharDeviceXXX_APP

    clean_app:

        rm NewCharDeviceXXX_APP

    8、添加“c_cpp_properties.json

    按下“Ctrl+Shift+P”,打开VSCode控制台,然后输入“C/C++:Edit Configurations(JSON)”,打开以后会自动在“.vscode ”目录下生成一个名为“c_cpp_properties.json” 的文件。

    修改c_cpp_properties.json内容如下所示:

    {

        "configurations": [

            {

                "name": "Linux",

                "includePath": [

                    "${workspaceFolder}/**",

                   "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31",

                    "/home/zgq/linux/Linux_Drivers/NewCharDeviceXXX",

    "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include",

    "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include",

    "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated"

    ],

                "defines": [],

                "compilerPath": "/usr/bin/gcc",

                "cStandard": "gnu11",

                "cppStandard": "gnu++14",

                "intelliSenseMode": "gcc-x64"

            }

        ],

        "version": 4

    }

    9、NewCharDeviceXXX.c文件如下:

    #include

    //数据类型重命名

    //使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t

    //使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t

    #include

    #include

    #include

    #include

    #include

    #include

    #include /*字符设备结构cdev定义在linux/cdev.h文件里*/

    #include //自动创建和删除“设备节点文件”

    #include /* 类class定义在linux/device.h文件里*/

    #define NewCharDeviceXXX_CNT    1   //定义设备数量为1

    #define NewCharDeviceXXX_NAME  "NewCharDeviceXXXName"//定义设备的名字

    /* 设备结构体 */

    struct CharDeviceXXX_dev{

      dev_t devid; /*声明32位变量devid用来给保存设备号 */

      int major; /* 主设备号 */

      int minor; /* 次设备号 */

      struct cdev  cdev; /*字符设备结构cdev定义在linux/cdev.h文件里*/

      struct class *class; /* 类 ,class定义在linux/device.h文件里*/

      struct device *device;/*设备*/

    };

    struct CharDeviceXXX_dev strCharDeviceXXX;

    static char CharDeviceXXX_readbuf[100]; //读缓冲区

    static char CharDeviceXXX_writebuf[100]; //写缓冲区

    static char My_DataBuffer[] = {"My Data!"};

    /* 打开设备 */

    static int CharDeviceXXX_open(struct inode *inode, struct file *filp)

    {

      /* 用户实现具体功能 */

      printk("CharDeviceXXX_open!\r\n");

      return 0;

    }

    /* 从设备读取数据,保存到首地址为buf的数据块中,长度为cnt个字节 */

    //file结构指针变量flip表示要打开的设备文件

    //buf表示用户数据块的首地址

    //cnt表示用户数据的长度,单位为字节

    //loff_t结构指针变量offt表示“相对于文件首地址的偏移”

    static ssize_t CharDeviceXXX_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)

    {

      int ret = 0;

      memcpy(CharDeviceXXX_readbuf, My_DataBuffer,sizeof(My_DataBuffer));

      //将My_DataBuffer[]中的所有数据拷贝到CharDeviceXXX_readbuf[]

      ret = copy_to_user( buf, CharDeviceXXX_readbuf, cnt );

      //将CharDeviceXXX_readbuf[]中的前cnt个字节拷贝到buf[]中

      if(ret==0) printk("Driver send the data to the user, and the result is ok!\r\n");

      else printk("Driver send the data to the user, and the result is failed!\r\n");

      return 0;

    }

    /* 向设备写数据,将数据块首地址为buf的数据,长度为cnt个字节,发送给用户 */

    //file结构指针变量flip表示要打开的设备文件

    //buf表示用户数据块的首地址

    //cnt表示用户数据的长度,单位为字节

    //loff_t结构指针变量offt表示“相对于文件首地址的偏移”

    static ssize_t CharDeviceXXX_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)

    {

      int ret = 0;

      ret = copy_from_user(CharDeviceXXX_writebuf, buf, cnt);

      //将buf[]中的前cnt个字节拷贝到CharDeviceXXX_writebuf[]中

      if(ret==0) printk("Driver receive the data form user , and the result is ok!\r\n");

      else printk("Driver receive the data form user , and the result is failed!\r\n");

      return 0;

    }

    /* 关闭/释放设备 */

    static int CharDeviceXXX_release(struct inode *inode, struct file *filp)

    {

      /* 用户实现具体功能 */

      printk("CharDeviceXXX_release!\r\n");

      return 0;

    }

    /*声明file_operations结构变量MyCharDevice_fops*/

    /*它是指向设备的操作函数集合变量*/

    const struct file_operations CharDeviceXXX_fops = {

      .owner = THIS_MODULE,

      .open = CharDeviceXXX_open,

      .read = CharDeviceXXX_read,

      .write = CharDeviceXXX_write,

      .release = CharDeviceXXX_release,

    };

    /*驱动入口函数 */

    static int  __init CharDeviceXXX_init(void)

    {

      int ret;

      /* 1、寄存器地址映射 */

      

      /*2、创建设备号*/

      strCharDeviceXXX.major=0;

      if(strCharDeviceXXX.major)/*如果指定了主设备号*/

      {

        strCharDeviceXXX.devid = MKDEV(strCharDeviceXXX.major, 0);

        //输入参数strCharDeviceXXX.major为“主设备号”

        //输入参数0为“次设备号”,大部分驱动次设备号都选择0

    //将strCharDeviceXXX.major左移20位,再与0相或,就得到“Linux设备号”

       

    ret=register_chrdev_region(strCharDeviceXXX.devid, NewCharDeviceXXX_CNT, NewCharDeviceXXX_NAME);

    //申请设备号

        //strCharDeviceXXX.devid表示起始设备号

        //NewCharDeviceXXX_CNT表示次设备号的数量

        //NewCharDeviceXXX_NAME表示设备名

        if(ret < 0) //申请设备号失败

          goto fail_map;

      }

      else

      { /* 没有定义设备号 */

        ret=alloc_chrdev_region(&strCharDeviceXXX.devid, 0, NewCharDeviceXXX_CNT,NewCharDeviceXXX_NAME);

    /* 申请设备号 */

        //strCharDeviceXXX.devid:保存申请到的设备号

        //0:次设备号的起始地址

        //NewCharDeviceXXX_CNT:要申请的次设备号数量;

        //NewCharDeviceXXX_NAME:表示“设备名字”

        if(ret < 0) //申请设备号失败

          goto fail_map;//去释放“物理地址内存映射”

    strCharDeviceXXX.major = MAJOR(strCharDeviceXXX.devid);

    /* 获取分配号的主设备号 */

    //输入参数strCharDeviceXXX.devid为“Linux设备号”

    //将strCharDeviceXXX.devid右移20位得到“主设备号”

    strCharDeviceXXX.minor = MINOR(strCharDeviceXXX.devid);

    /* 获取分配号的次设备号 */

       //输入参数strCharDeviceXXX.devid为“Linux设备号”

       //将strCharDeviceXXX.devid与0xFFFFF相与后得到“次设备号”

      }

      /*3、注册字符设备*/

      strCharDeviceXXX.cdev.owner = THIS_MODULE;

    //使用THIS_MODULE将owner指针指向当前这个模块

      cdev_init(&strCharDeviceXXX.cdev,&CharDeviceXXX_fops);

      //注册字符设备,初始化“字符设备结构变量strCharDeviceXXX.cdev”

      //strCharDeviceXXX.cdev是等待初始化的结构体变量

      //CharDeviceXXX_fops就是字符设备文件操作函数集合

    /*4、添加字符设备cdev*/      ret=cdev_add(&strCharDeviceXXX.cdev,strCharDeviceXXX.devid,NewCharDeviceXXX_CNT);

    //添加字符设备

    /*&strCharDeviceXXX.cdev表示指向要添加的字符设备,即字符设备结构strCharDeviceXXX.cdev变量*/

    //strCharDeviceXXX.devid表示设备号

    //NewCharDeviceXXX_CNT表示需要添加的设备数量

      if(ret < 0 ) //添加字符设备失败

        goto del_register;//去执行删除“已经注册的字符设备”

      printk("dev id major = %d,minor = %d\r\n", strCharDeviceXXX.major, strCharDeviceXXX.minor);

      printk("CharDeviceXXX_init is ok!!!\r\n");

      /*5、创建类*/

      strCharDeviceXXX.class = class_create(THIS_MODULE, NewCharDeviceXXX_NAME);

    //创建类

    //使用THIS_MODULE将owner指针指向当前这个模块

    //NewCharDeviceXXX_NAME是类名字

    //返回值是指向结构体class的指针,也就是创建的类

      if(IS_ERR(strCharDeviceXXX.class)){

        goto del_cdev; //去执行删除“已添加的字符设备”

      }

      /*6、创建设备 */

      strCharDeviceXXX.device = device_create(strCharDeviceXXX.class, NULL, strCharDeviceXXX.devid, NULL, NewCharDeviceXXX_NAME);

    //创建设备

    //设备要创建在strCharDeviceXXX.class类下面

    //NULL表示没有父设备

    //strCharDeviceXXX.devid是设备号;

    //参数drvdata=NULL,设备没有使用数据

    //NewCharDeviceXXX_NAME是设备名字

    /*如果设置fmt=NewCharDeviceXXX_NAME 的话,就会生成/dev/NewCharDeviceXXX_NAME设备文件*/

      //返回值就是创建好的设备。

       if(IS_ERR(strCharDeviceXXX.device)){

         goto destroy_class;

      }

      return 0;//驱动初始化正确

    destroy_class:

      class_destroy(strCharDeviceXXX.class);

      //删除类

      //strCharDeviceXXX.class就是要删除的类

    del_cdev:

       cdev_del(&strCharDeviceXXX.cdev);

       //删除字符设备

       //&strCharDeviceXXX.cdev表示指向需要删除的字符设备,即字符设备结构strCharDeviceXXX.cdev变量

    del_register:

      unregister_chrdev_region(strCharDeviceXXX.devid, NewCharDeviceXXX_CNT);

    /* 释放设备号 */

    //strCharDeviceXXX.devid:需要释放的起始设备号

    //NewCharDeviceXXX_CNT:需要释放的次设备号数量;

    fail_map://申请设备号失败

      /*若有物理地址映射到内存,则释放内存*/

      return -EIO; //驱动初始化失败

    }

    /*驱动出口函数 */

    static void __exit CharDeviceXXX_exit(void)

    {

    /*1、释放内存*/

      /*2、 释放设备号 */

    unregister_chrdev_region(strCharDeviceXXX.devid,NewCharDeviceXXX_CNT);

    /* 释放设备号 */

    //strCharDeviceXXX.devid:需要释放的起始设备号

    //NewCharDeviceXXX_CNT:需要释放的次设备号数量;

    /*3、删除字符设备*/

      cdev_del(&strCharDeviceXXX.cdev);

     /*删除字符设备*/

     /*&strCharDeviceXXX.cdev表示指向需要删除的字符设备,即字符设备结构strCharDeviceXXX.cdev变量*/

    /*4、 删除设备 */

    device_destroy(strCharDeviceXXX.class, strCharDeviceXXX.devid);

    //删除创建的设备

    //newchrled.class是要删除的设备所处的类

    //newchrled.devid是要删除的设备号

    /*5、删除类*/

    class_destroy(strCharDeviceXXX.class);

    //删除类

    //strCharDeviceXXX.class就是要删除的类

    }

    module_init(CharDeviceXXX_init);

    //指定CharDeviceXXX_init()为驱动入口函数

    module_exit(CharDeviceXXX_exit);

    //指定CharDeviceXXX_exit()为驱动出口函数

    MODULE_AUTHOR("Zhanggong");//添加作者名字

    MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”

    MODULE_INFO(intree,"Y");

    //去除显示“loading out-of-tree module taints kernel.”

    10、编译

    由于NewCharDeviceXXX_APP.c和CharDeviceXXX_APP.c内容相同,只是修改了文件名,不再重写,参考前面的文章“Linux第68步_旧字符设备驱动的一般模板”

    输入“make clean_drv回车”,清除NewCharDeviceXXX.*

    输入“make drv回车”,编译生成NewCharDeviceXXX.ko

    输入“make clean_app回车”,清除NewCharDeviceXXX_APP

    输入“make app回车”,编译生成NewCharDeviceXXX_APP

    输入“ls -l回车

    输入“sudo cp NewCharDeviceXXX.ko NewCharDeviceXXX_APP /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -f回车

    11、测试

    启动开发板,从网络下载程序

    输入“root

    输入“cd /lib/modules/5.4.31/回车

    切换到“/lib/modules/5.4.31/”目录

    注意:“lib/modules/5.4.31/在虚拟机中是位于“/home/zgq/linux/nfs/rootfs/”目录下,但在开发板中,却是位于根目录中

    输入“ls”查看“NewCharDeviceXXX.ko和NewCharDeviceXXXApp”是否存在

    输入“depmod”,驱动在第一次执行时,需要运行“depmod”

    输入“modprobe NewCharDeviceXXX.ko”,加载“NewCharDeviceXXX.ko”模块

    输入“lsmod”查看有哪些驱动在工作

    输入“ls /dev/NewCharDeviceXXXName -l回车”,发现节点文件“/dev/NewCharDeviceXXXName

    输入“./NewCharDeviceXXX_APP /dev/NewCharDeviceXXXName 1回车”执行读操作

    输入“./NewCharDeviceXXX_APP /dev/NewCharDeviceXXXName 2回车”执行写操作

    输入“rmmod NewCharDeviceXXX.ko”,卸载“NewCharDeviceXXX.ko”模块

    注意:输入“rmmod NewCharDeviceXXX”也可以卸载“NewCharDeviceXXX.ko”模块

    输入“lsmod”查看有哪些驱动在工作。

    输入“ls /dev/NewCharDeviceXXXName -l回车”,查询节点文件“/dev/NewCharDeviceXXXName”是否存在

  • 相关阅读:
    PMP每日一练 | 考试不迷路-11.24(包含敏捷+多选)
    【QT--使用百度地图API显示地图并绘制路线】
    模式分类识别 | Python实现基于Xboost的股票走势识别预测
    Day116.尚医通:预约挂号详情 ※
    BP神经网络简单应用实例,BP神经网络训练函数
    分布式ID生成方案总结整理
    MySQ 学习笔记
    CMSC5707-高级人工智能之特征表示和压缩
    刷题记录(NC16708 过河卒,NC16619 传球游戏,NC16810 [NOIP1999]拦截导弹)
    实现一个宽高自适应的正方形
  • 原文地址:https://blog.csdn.net/weixin_42550185/article/details/136470506