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

news2024/9/24 1:15:06

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 <linux/types.h>

//数据类型重命名

//使能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 <linux/kernel.h>

#include <linux/delay.h>

#include <linux/ide.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/string.h>

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

#include <linux/mdev.h>//自动创建和删除“设备节点文件”

#include <linux/device.h>/* 类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”是否存在

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1490174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java设计模式】五、建造者模式

文章目录 1、建造者模式2、案例&#xff1a;共享单车的创建3、其他用途 1、建造者模式 某个对象的构建复杂将复杂的对象的创建 和 属性赋值所分离&#xff0c;使得同样的构建过程可以创建不同的表示建造的过程和细节调用者不需要知道&#xff0c;只需要通过构建者去进行操作 …

Spring(22) Spring中的9种设计模式

目录 一、简单工厂模式&#xff08;Simple Factory&#xff09;二、工厂方法模式&#xff08;Factory Method&#xff09;三、单例模式&#xff08;Singleton&#xff09;四、适配器模式&#xff08;Adapter&#xff09;五、代理模式&#xff08;Proxy&#xff09;七、观察者模…

将jar包打包成exe可执行文件的工具介绍

在Java开发中&#xff0c;将.jar包打包成可执行的.exe文件是一种常见的需求&#xff0c;尤其是在需要将Java应用程序分发给没有安装Java虚拟机&#xff08;JVM&#xff09;的普通用户时。有多种工具可以将Java应用程序打包成.exe文件&#xff0c;这些工具通常使用Java的launch4…

从Win转Mac,我的感受如何

文章目录 前言MacBook优点美观动画流畅安装软件方便轻便、续航强大多数命令和Linux通用系统稳定、安全做工精美、视听体验好CPU性能较好触控板体验好 MacBook缺点缺乏部分软件部分操作逻辑不是很科学&#xff1f;玩不了多少游戏 总结与展望 前言 整个大学期间&#xff0c;我的主…

Flutter中的Provider状态管理工具有哪些优势

在Flutter应用开发中&#xff0c;状态管理是一个至关重要的方面。而Provider作为一种简单、灵活且高效的状态管理工具&#xff0c;在众多Flutter开发者中备受青睐。本文将深入探讨Provider在Flutter中的优势&#xff0c;帮助读者更好地理解其价值和应用场景。 简单易用 Provi…

最全AI领域知识星球:GoAI的学习社区

最全AI领域知识星球&#xff1a;GoAI的学习社区 【作者及星球介绍】 &#x1f468;‍&#x1f4bb;作者简介&#xff1a; CSDN、阿里云人工智能领域博客专家&#xff0c;新星计划计算机视觉导师&#xff0c;百度飞桨PPDE&#xff0c;专注大数据与AI知识分享。 ✨公众号&#x…

深度学习算法优化流程

深度学习算法的一般优化流程&#xff0c;具体的实施方法和步骤可能会根据具体任务和数据的特点而有所不同&#xff0c;优化流程通常包括以下几个主要步骤&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作…

产品推荐 - GX-SOPC-5CEFA5-M484 FPGA核心开发板

● 核心板采用8层板精心设计 ● FPGA&#xff1a;采用Intel&#xff08;ALTERA&#xff09; Cyclone V 5CEFA5&#xff0c;Les为77K&#xff0c;内嵌存储器为4460Kb&#xff0c;硬件乘法器为300个&#xff0c;最大等效门数约2300万门&#xff1b;新增DSP Block&#xff08;150…

STM32基础--初识 STM32

什么是 STM32 对于STM32&#xff0c;从字面意思上来理解&#xff0c;ST是意法半导体&#xff0c;M是Microelectronics的缩写&#xff0c;其中32表示的是32位&#xff0c;那么整合起来理解就是&#xff1a;STM32就是指的ST公司开发的32位微控制器。在如今的32位控制器中&#x…

IPD MM流程之业务策略工具:安索夫矩阵

IPD市场管理流程&#xff0c;华为内部称为“MM流程”&#xff08;Market Management&#xff0c;MM&#xff09;。华为市场管理是通过对市场和细分市场的分析&#xff0c;制定细分市场的策略&#xff0c;形成商业计划&#xff0c;把商业计划落实在日常工作当中。MM流程其中一个…

原始手写helloworld并打jar包允许

1.创建文件夹test统一在其中操作 2.创建hello.java文件 【hello.txt改属性为hello.java】并在里面添加代码 public class hello {public static void main(String[] args) {System.out.println("hello world");} } 注意&#xff1a;类名与文件名一致 然后运行…

动手学深度学习—循环神经网络RNN详解

循环神经网络 循环神经网络的步骤&#xff1a; 处理数据 将数据按照批量大小和时间步数进行处理&#xff0c;最后得到迭代器&#xff0c;即每一个迭代的大小是批量大小时间步数&#xff0c;迭代次数根据整个数据的大小决定&#xff0c;最后得出处理的数据&#xff08;参照第三…

13 丢弃法dropout【李沐动手学深度学习v2笔记】

1. 丢弃法 在层之间加入随机噪音 加入噪音的一些规则 加入噪音后不要改变期望 使用丢弃法 推理中的丢弃法 总结 2. 代码实现 4.6. 暂退法&#xff08;Dropouthttps://zh.d2l.ai/chapter_multilayer-perceptrons/dropout.html 2.1 Dropout import torch from torch import n…

两天学会微服务网关Gateway-Gateway过滤器

锋哥原创的微服务网关Gateway视频教程&#xff1a; Gateway微服务网关视频教程&#xff08;无废话版&#xff09;_哔哩哔哩_bilibiliGateway微服务网关视频教程&#xff08;无废话版&#xff09;共计17条视频&#xff0c;包括&#xff1a;1_Gateway简介、2_Gateway工作原理、3…

windows下thinkphp使用php7.4.5版本链接oracle数据库

我用的php运行环境是PHPCUSTOM&#xff0c;感谢大佬Lccee的耐心指导。 大佬的博客https://blog.csdn.net/Lccee?typeblog 首先查看自己的oracle版本 查询语句: SELECT * FROM v$version;根据自己的版本下载对应的oracle客户端&#xff0c;及得版本运行环境与自己的环境位数要…

智慧城市中的数字孪生:数字孪生技术助力智慧城市提高公共服务水平

目录 一、引言 二、数字孪生技术概述 三、数字孪生技术在智慧城市中的应用 1、智慧交通管理 2、智慧能源管理 3、智慧环保管理 4、智慧公共安全 四、数字孪生技术助力智慧城市提高公共服务水平的价值 五、挑战与前景 六、结论 一、引言 随着信息技术的飞速发展&…

华为Web举例:私网用户通过NAT No-PAT访问Internet(访问明确的目的Server)

Web举例&#xff1a;私网用户通过NAT No-PAT访问Internet(访问明确的目的Server) 介绍私网用户通过NAT No-PAT访问Internet的配置举例。 组网需求 某工作室在网络边界处部署了FW作为安全网关。为了使私网中10.1.1.0/24网段的用户可以正常访问Internet&#xff0c;需要在FW上配…

【AI视野·今日CV 计算机视觉论文速览 第300期】Fri, 1 Mar 2024

AI视野今日CS.CV 计算机视觉论文速览 Fri, 1 Mar 2024 Totally 114 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers DistriFusion: Distributed Parallel Inference for High-Resolution Diffusion Models Authors Muyang Li, Tianle Cai, J…

EXPLAIN PLAN FOR:在Oracle中生成执行计划

目录 案例 解析 Operation类型 在Oracle中&#xff0c;可以使用 EXPLAIN PLAN FOR 命令来生成执行计划&#xff0c;然后通过 SELECT plan_table_output FROM TABLE(DBMS_XPLAN.DISPLAY(PLAN_TABLE))来查看执行计划。需要注意的是&#xff0c;这两个命令需要在同一个窗口下运…

基于MVO优化的Bi-LSTM多输入回归预测(Matlab)多元宇宙算法优化长短期神经网络回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、算法介绍&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译&am…