Linux第66步_linux字符设备驱动_挂载和卸载

1、了解linux中的驱动类型:

1)、字符设备驱动

字符设备是limnux驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。如:GPIO输入输出、UART、I2C、SPI、USB、LCD、音频等都属于字符设备驱动。

2)、块设备驱动

块设备驱动就是存储器设备的驱动,比如EMMC、NAND、SD卡和U盘等存储设备,因为这些存储设备的特点是以存储块为基础,因此叫做“块设备

3)、网络设备驱动网络设备就是网络驱动,不管是有线的,还是无线的,都属于网络设备驱动的范畴。

注意:

一个设备可以属于多种设备驱动类型,比如:USB WIFI,其使用USB接口,所以属于字符设备,但是其又能上网,所以它又属于网络设备驱动。

2、了解Limux应用程序调用驱动程序的流程

在Linux中一切皆为文件。驱动加载成功以后会在“/dev”目录下生成一个相应的驱动文件,如“xxx”的驱动文件名。应用程序通过对“/dev/xxx”的文件进行相应的操作,就可实现对硬件的操作。比如:现在有个叫做“/dev/led ”的驱动文件,它是1ed灯的驱动文件。应用程序使用“open函数”打开驱动文件“/dev/led ”,然后使用“close函数”关闭驱动文件“/dev/led”。open和close 就是打开和关闭led驱动的函数,如果要点亮或关闭1ed,那么就使用write函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开led的控制参数。如果要获取led灯的状态,就用read函数从驱动中读取相应的状态。应用程序运行在用户空间,而limux驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核进行操作,比如使用open函数打开/dev/led这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。open、close、write和read等这些函数是由C库提供的,在Limux系统中,系统调用作为C库的一部分。

我们重点关注的时应用程序调用的函数驱动调用的函数,至于C库和内核调用的函数,我们不用去关心。

3、了解“file_operations”结构体

打开虚拟机上VSCode,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“atk-mp1”,点击“linux”,点击“my_linux”,点击“linux-5.4.31”,见下图:

点击“确定

点击“查看”,点击“搜索”,输入“struct file_operations

随便点击其中一个struct file_operations”,得到下面的界面:

在“file_operations”点击“鼠标右键”,点击“转到定义”,见下图:

得到下图:

字符设备驱动重点是“file_operations”结构体

“fs.h”定义“file_operations”结构体如下:

struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);

ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);

int (*iopoll)(struct kiocb *kiocb, bool spin);

int (*iterate) (struct file *, struct dir_context *);

int (*iterate_shared) (struct file *, struct dir_context *);

__poll_t (*poll) (struct file *, struct poll_table_struct *);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

unsigned long mmap_supported_flags;

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, loff_t, loff_t, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

int (*check_flags)(int);

int (*flock) (struct file *, int, struct file_lock *);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

int (*setlease)(struct file *, long, struct file_lock **, void **);

long (*fallocate)(struct file *file, int mode, loff_t offset,

  loff_t len);

void (*show_fdinfo)(struct seq_file *m, struct file *f);

#ifndef CONFIG_MMU

unsigned (*mmap_capabilities)(struct file *);

#endif

ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,

loff_t, size_t, unsigned int);

loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,

   struct file *file_out, loff_t pos_out,

   loff_t len, unsigned int remap_flags);

int (*fadvise)(struct file *, loff_t, loff_t, int);

} __randomize_layout;

/* 设备操作函数结构体 */

static struct file_operations chrdevbase_fops = {

    .owner = THIS_MODULE,  

    .open = chrdevbase_open,

    .read = chrdevbase_read,

    .write = chrdevbase_write,

    .release = chrdevbase_release,

};

4、了解“linux-5.4.31”中的驱动

下面我们通过“crc-vpmsum_test.c”来学习linux驱动的编写:

1)、点击“转到”,点击“转到文件”,在输入框中输入“crc-vpmsum_test.c”,按“回车键”打开“crc-vpmsum_test.c

见下图:

crc-vpmsum_test.c”内容如下:

// SPDX-License-Identifier: GPL-2.0-only

/*

 * CRC vpmsum tester

 * Copyright 2017 Daniel Axtens, IBM Corporation.

 */

#include <linux/crc-t10dif.h>

#include <linux/crc32.h>

#include <crypto/internal/hash.h>

#include <linux/init.h>       //必须要包含的头文件

#include <linux/module.h>     //必须要包含的头文件

#include <linux/string.h>     //下面要用到字符串,显然也要包含

#include <linux/kernel.h>     //必须要包含的头文件

#include <linux/cpufeature.h>

#include <asm/switch_to.h>

static unsigned long iterations = 10000;

#define MAX_CRC_LENGTH 65535

/*入口函数初始化*/

static int __init crc_test_init(void)

{

u16 crc16 = 0, verify16 = 0;

u32 crc32 = 0, verify32 = 0;

__le32 verify32le = 0;

unsigned char *data;

unsigned long i;

int ret;

struct crypto_shash *crct10dif_tfm;

struct crypto_shash *crc32c_tfm;

if (!cpu_has_feature(CPU_FTR_ARCH_207S))

return -ENODEV;

data = kmalloc(MAX_CRC_LENGTH, GFP_KERNEL);

if (!data)

return -ENOMEM;

crct10dif_tfm = crypto_alloc_shash("crct10dif", 0, 0);

if (IS_ERR(crct10dif_tfm)) {

pr_err("Error allocating crc-t10dif\n");

goto free_buf;

}

crc32c_tfm = crypto_alloc_shash("crc32c", 0, 0);

if (IS_ERR(crc32c_tfm)) {

pr_err("Error allocating crc32c\n");

goto free_16;

}

do {

SHASH_DESC_ON_STACK(crct10dif_shash, crct10dif_tfm);

SHASH_DESC_ON_STACK(crc32c_shash, crc32c_tfm);

crct10dif_shash->tfm = crct10dif_tfm;

ret = crypto_shash_init(crct10dif_shash);

if (ret) {

pr_err("Error initing crc-t10dif\n");

goto free_32;

}

crc32c_shash->tfm = crc32c_tfm;

ret = crypto_shash_init(crc32c_shash);

if (ret) {

pr_err("Error initing crc32c\n");

goto free_32;

}

pr_info("crc-vpmsum_test begins, %lu iterations\n", iterations);

for (i=0; i<iterations; i++) {

size_t offset = prandom_u32_max(16);

size_t len = prandom_u32_max(MAX_CRC_LENGTH);

if (len <= offset)

continue;

prandom_bytes(data, len);

len -= offset;

crypto_shash_update(crct10dif_shash, data+offset, len);

crypto_shash_final(crct10dif_shash, (u8 *)(&crc16));

verify16 = crc_t10dif_generic(verify16, data+offset, len);

if (crc16 != verify16) {

pr_err("FAILURE in CRC16: got 0x%04x expected 0x%04x (len %lu)\n",

       crc16, verify16, len);

break;

}

crypto_shash_update(crc32c_shash, data+offset, len);

crypto_shash_final(crc32c_shash, (u8 *)(&crc32));

verify32 = le32_to_cpu(verify32le);

        verify32le = ~cpu_to_le32(__crc32c_le(~verify32, data+offset, len));

if (crc32 != (u32)verify32le) {

pr_err("FAILURE in CRC32: got 0x%08x expected 0x%08x (len %lu)\n",

       crc32, verify32, len);

break;

}

}

pr_info("crc-vpmsum_test done, completed %lu iterations\n", i);

} while (0);

free_32:

crypto_free_shash(crc32c_tfm);

free_16:

crypto_free_shash(crct10dif_tfm);

free_buf:

kfree(data);

return 0;

}

/*出口函数初始化*/

static void __exit crc_test_exit(void) {}

module_init(crc_test_init);/*将crc_test_init()指定为入口函数*/

module_exit(crc_test_exit); /*将crc_test_exit()指定为出口函数*/

module_param(iterations, long, 0400);

MODULE_AUTHOR("Daniel Axtens <dja@axtens.net>");//添加作者名字

MODULE_DESCRIPTION("Vector polynomial multiply-sum CRC tester");

//从字面意思上看,指的是模块介绍

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

上面的程序用到“pr_err()和pr_info()”,通过查找,它们都是调用“printk()”,内容如下:

#define pr_err(fmt, ...) \

printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)

#define pr_info(fmt, ...) \

printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

因此,我们知道linux的串口输出是调用printk()来实现的,相当于C语言的printf()函数。

Linux驱动的运行方式:

1)、将驱动源码编译进Linux内核中,生成uImage/zImage系统镜像里面。

2)、将驱动源码编译成模块生成以“.ko结尾的文件然后在Linux内核加载驱动模块。也可以编译进内核,最终集成到uImage里面。

4、加载模块使用modprobe命令卸载模块使用insmod命令

比如执行“insmod drv.ko”加载“drv.ko”模块,由于drv.ko模块需要依赖first.ko模块,因此,要先执行“insmod first.ko”,然后再执行“insmod drv.ko”,才可以加载。而执行“modprobe drv.ko”加载“drv.ko”模块,它会分析first.kodrv.ko模块之间的依赖关系,自动将“first.kodrv.ko”加载到内核中。因此加载驱动使用modprobe命令

在卸载的时候,执行“rmmod drv.ko”就可以卸载了,而modprobe命令在分析出“first.kodrv.ko”模块之间有依赖关系,若first.ko被其它模块使用le,就不能执行“modprobe -r drv;若first.ko没有被其它模块使用,就可以执行“modprobe -r drv;。因此卸载模块使用insmod命令

注意:

Limnux kemnel的版本号为5.4.31modprobe命令会到“/lib/modules/5.4.31”目录中查找相应的驱动模块,因此我们需要将编写好的驱动会放到“/lib/modules/5.4.31”目录中

5、通过“crc-vpmsum_test.c”学习,我们仿写一个驱动,命名为My_TestDriver.c。

1)、创建“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录

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

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

输入“mkdir 00_My_TestDriver

创建“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录

输入“ls”,查询“/home/zgq/linux/Linux_Drivers/”目录下的文件和文件夹

输入“cd 00_My_TestDriver/

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

输入“vi My_TestDriver.c”打开“My_TestDriver.c”

输入内容如下:

#include <linux/init.h>       //必须要包含的头文件

#include <linux/module.h>     //必须要包含的头文件

#include <linux/string.h>     //下面要用到字符串,显然也要包含

#include <linux/kernel.h>     //必须要包含的头文件

/*入口函数初始化*/

static int __init My_TestDriver_init(void)

{

int ret = 0;

    printk("My_TestDriver_init\r\n");

return ret;

}

/*出口函数初始化*/

static void __exit My_TestDriver_exit(void)

{

printk("My_TestDriver_exit\r\n");

}

module_init(My_TestDriver_init);/*将My_TestDriver_init()指定为入口函数*/

module_exit(My_TestDriver_exit); /*将My_TestDriver_exit()指定为出口函数*/

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

MODULE_DESCRIPTION("This is test module!");//模块介绍

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

MODULE_INFO(intree,"Y");//去处显示“loading out-of-tree module taints kernel.”

按“ESC键”,输入“:wq回车

输入“vim Makefile

添加内容如下:

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

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

CURRENT_PATH := $(shell pwd)

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

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

obj-m := My_TestDriver.o

#生成“obj-m”需要依赖“My_TestDriver.o”

build: kernel_modules

#生成“build”需要依赖“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/00_My_TestDriver”
        @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:

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

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

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

按“ESC键”,输入“:wq回车

在linx驱动中的Makefile文件,有点像“八股文”,在格式上基本是固定的。

驱动测试

在连接开发板之前,需要将“My_TestDriver.ko”拷贝到“/home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/

输入“cd /home/zgq/linux/Linux_Drivers/00_My_TestDriver/

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

输入“ls”,查询“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录下的文件和文件夹

输入“make clean回车”清除工程

输入“make回车”执行编译

输入“ls”,查询“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录下的文件和文件夹

输入“sudo cp  My_TestDriver.ko  /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31

将“My_TestDriver.ko”拷贝到“/home/zgq/linux/nfs/rootfs/lib/modules/5.4.31

 

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

输入“root”

输入“cd /lib/modules/5.4.31/

在nfs挂载中,切换到“/home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/”目录

输入“ls

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

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

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

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

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

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

至此,我们完成了linux字符设备驱动的挂载和卸载。

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

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

相关文章

基于相位的运动放大:如何检测和放大难以察觉的运动(01/2)

基于相位的运动放大&#xff1a;如何检测和放大难以察觉的运动 目录 一、说明二、结果的峰值三、金字塔背景3.1 可操纵金字塔3.2 亚倍频程复数可控金字塔 四、基本方针4.1 1D 问题陈述4.2 一维方法4.3 实际实施说明 五、放大倍率的限制5.1 空间支持的影响5.2 频带的影响 六、推…

好书推荐丨细说Python编程:从入门到科学计算

文章目录 写在前面Python简介推荐图书内容简介编辑推荐作者简介 推荐理由粉丝福利写在最后 写在前面 本期博主给大家推荐一本Python基础入门的全新正版书籍&#xff0c;对Python、机器学习、人工智能感兴趣的小伙伴们快来看看吧~ Python简介 Python 是一种广泛使用的高级、解…

多目标追踪概述

1. 目标跟踪分类 单目标跟踪&#xff1a;在视频的初始帧画面上框出单个目标&#xff0c;预测后续帧中该目标的大小与位置多目标跟踪&#xff1a;追踪多个目标的大小和位置&#xff0c;且每一帧中目标的数量和位置都可能变化 2. 多目标跟踪目前的主要问题 形态变化&#xff1a…

做了个很牛的网站,可以搜索网站的网站到底有多好用?

今天给大家推荐的网站叫做&#xff1a;毒蘑菇 - 搜索 毒蘑菇搜索&#xff0c;顾名思义呢&#xff0c;搜索的功能比较好用&#xff0c;大家上网的时候总是需要记住网站的地址&#xff0c;即使你知道网站的名称&#xff0c;也得跳转到百度然后在搜索&#xff0c;有时候百度上那么…

天哪!还有这些逆天的fofa​语句?(二)

接上文 天哪&#xff01;还有这些逆天的fofa语句&#xff1f; 再分享几条&#xff0c;个人觉得比较有意思的fofa语句。 情侣飞行器 之前写过文章的&#xff0c;有兴趣的师傅可以试着翻翻以前的文章去破解密码 fofa语句&#xff1a;"static/js/index.d2dcdf5b.js"…

sql-labs第46关(order by盲注脚本)

一、环境 网上有自己找 二、解释 order by 注入我们看他的true和false来进行注入出来 二、实操 让我们用sort 看看源码 最终我们的id是放到order by后面了 如果我们直接用列去排序 ?sortusername/password username&#xff1a; password&#xff1a; 可以看到顺序是不…

一个39岁程序员的自白,大龄程序员的出路在哪里?

一个39岁程序员的自白&#xff0c;大龄程序员的出路在哪里&#xff1f; 大龄程序员&#xff0c;最悲惨的&#xff0c;可能是但凡你发个贴&#xff0c;下面就会有类似这种人来怼你 本文来自知乎一个大龄程序员老哥&#xff08;白圣君&#xff09;的自白&#xff0c;涤生哥已经经…

JAVA集合进阶(Set、Map集合)

一、Set系列集合 1.1 认识Set集合的特点 Set集合是属于Collection体系下的另一个分支&#xff0c;它的特点如下图所示 下面我们用代码简单演示一下&#xff0c;每一种Set集合的特点。 //Set<Integer> set new HashSet<>(); //无序、无索引、不重复 //Set<…

【架构】GPU架构总结

文章目录 GPU架构GPU渲染内存架构Streaming Multiprocessor(SM)CUDA CoreTensor CoreRT CoreCPU-GPU异构系统GPU资源管理模型 GPU架构演进G80 架构Fermi 架构Maxwell架构Tesla架构Pascal架构Volta 架构Turing架构Ampere 架构Hopper架构 参考文献 GPU架构 主要组成包括&#xf…

外汇天眼:掌握这个方法,你也能成为交易高手!

在金融市场这个大潮中&#xff0c;外汇交易因其高杠杆、24小时交易等特点吸引着无数交易者。然而成功的交易并非易事&#xff0c;对于投资者来说&#xff0c;外汇交易市场是一个复杂且多变的市场&#xff0c;要在外汇市场中获得成功就需要扎实的外汇金融基础知识和独特的策略&a…

压力测试工具Jmeter的下载与使用

1、进入官网下载Jmeter https://jmeter.apache.org/ 国内镜像&#xff08;下载的慢的话可以用国内镜像下载&#xff09; https://mirrors.cloud.tencent.com/apache/jmeter/binaries/ 2、跳转到下载页面 3、根据不同系统下载相应版本的Jmeter压缩包&#xff0c;Linux系统下载…

医院LIS(全称Laboratory Information Management System)系统源码

目录 一、医院LIS系统概况 二、医院LIS系统建设必要性 三、为什么要使用LIS系统 四、技术框架 &#xff08;1&#xff09;总体框架 &#xff08;2&#xff09;技术细节 &#xff08;3&#xff09;LIS主要功能模块 五、LIS系统优势 &#xff08;1&#xff09;客户/用户…

LeetCode_Java_环形链表(题目+思路+代码)

141.环形链表 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位…

49.仿简道云公式函数实战-文本函数-Ip

1. Ip函数 获取当前用户的ip地址 注意是Ipv4的地址 2. 函数用法 IP() 3. 函数示例 获取当前用户的ip地址IP() 4. 代码实战 首先我们在function包下创建text包&#xff0c;在text包下创建IpFunction类&#xff0c;代码如下&#xff1a; package com.ql.util.express.sel…

如何使用Lychee+cpolar搭建本地私人图床并实现远程访问存储图片

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站&#xff0c;可以看做是云存储的一部分&#xff0c;既可…

EAP-TLS实验之Ubuntu20.04环境搭建配置(FreeRADIUS3.0)(二)

上篇文章简要介绍了freeradius的搭建及配置&#xff0c;在最后数据库连接阶段还没进行测试验证&#xff0c;今天继续。 修改相关文件 1 radiusd.conf 打开762行注释&#xff08;&#xff04;INCLUDE mods-enabled/sql&#xff09;&#xff1b; 2 sites-available/default …

【大数据】Flink SQL 语法篇(四):Group 聚合、Over 聚合

Flink SQL 语法篇&#xff08;四&#xff09;&#xff1a;Group 聚合、Over 聚合 1.Group 聚合1.1 基础概念1.2 窗口聚合和 Group 聚合1.3 SQL 语义1.4 Group 聚合支持 Grouping sets、Rollup、Cube 2.Over 聚合2.1 时间区间聚合2.2 行数聚合 1.Group 聚合 1.1 基础概念 Grou…

DevOps 周期的 6 个 C

中型到大型软件开发项目涉及许多人员、多个团队、资源、工具和开发阶段。它们都需要以某种方式进行管理和简化&#xff0c;不仅可以获得所需的产品&#xff0c;而且还要确保将来在不断变化的环境下易于管理和维护。组织通常遵循许多项目管理模型和技术。DevOps 是其中之一&…

家用超声波清洗机哪个好?四款高评分超声波清洗机分享

超声波清洗机可以说是眼镜党家中必备的一款超声波清洗机&#xff0c;毕竟它能高效的帮我们解决清洗眼镜的烦恼&#xff0c;也可以帮我们清洗家中其他的一些物品。很多朋友因为各种原因没有时间清洗眼镜以及家中的小物件物品&#xff0c;长时间下来一次物品或者是眼镜上就会堆积…

卷积神经网络 CNN

目录 卷积网络与传统网络的区别 参数共享 卷积神经网络整体架构 卷积操作的作用 卷积核的定义 卷积特征值计算方法 卷积层涉及的参数 边缘填充 ​编辑 卷积结果计算 池化层 整体网格架构 VGG网络架构 残差网络Resnet 卷积网络与传统网络的区别 卷积神经网络&#x…