当前位置: 首页 > news >正文

第20章 原子操作实验(iTOP-RK3568开发板驱动开发指南 )

在上一章节的实验中,对并发与竞争进行了实验,两个app应用程序之间对共享资源的竞争访问引起了数据传输错误,而在Linux内核中,提供了四种处理并发与竞争的常见方法,分别是原子操作、自旋锁、信号量、互斥体,在之后的几个章节中会依次对上述四种方法进行讲解。

本章首先对四种常见方法中的原子操作进行讲解。

20.1 原子操作

“原子”是化学世界中不可再分的最小微粒,一切物质都由原子组成。在Linux内核中的原子操作可以理解为“不可被拆分的操作”,就是不能被更高等级中断抢夺优先的操作。在C语言中可以使用以下代码对一个整形变量赋值。

int v;//定义一个int类型的变量v
v = 1;//将int类型的变量v赋值为1

而上述代码仍然不是“不可拆分的操作”,C语言程序仍然需要翻译成汇编指令,在汇编指令的执行过程中仍可能会有竞争的产生。而原子操作会将整形变量的操作当成一个整体,不可再进行分割。而原子操作又可以进一步细分为“整型原子操作”和“位原子操作”,这里首先对****整型原子操作****进行讲解。

在Linux内核中使用 atomic_t和atomic64_t结构体分别来完成32位系统和64位系统的整形数据原子操作,两个结构体定义在“内核源码/include/linux/types.h”文件中,具体定义如下:

 typedef struct {int counter;} atomic_t;#ifdef CONFIG_64BITtypedef struct {long counter;
} atomic64_t;#endif

例如可以使用以下代码定义一个64位系统的原子整形变量:

atomic64_t v;

在成功定义原子变量之后,必然要对原子变量进行读取、加减等动作,原子操作的部分常用API函数如下所示,定义在“内核源码/include/linux/atomic.h”文件中,所以在接下来的实验中需要加入该头文件的引用。

函数描述
ATOMIC_INIT(int i)定义原子变量的时候对其初始化,赋值为i
int atomic_read(atomic_t *v)读取v的值,并且返回。
void atomic_set(atomic_t *v, int i)向原子变量v写入i值。
void atomic_add(int i, atomic_t *v)原子变量v加上i值。
void atomic_sub(int i, atomic_t *v)原子变量v减去i值。
void atomic_inc(atomic_t *v)原子变量v加1
void atomic_dec(atomic_t *v)原子变量v减1
int atomic_dec_return(atomic_t *v)原子变量v减1,并返回v的值。
int atomic_inc_return(atomic_t *v)原子变量v加 1,并返回v的值。
int atomic_sub_and_test(int i, atomic_t *v)原子变量v减 i,如果结果为0就返回真,否则返回假
int atomic_dec_and_test(atomic_t *v)原子变量v减 1,如果结果为0就返回真,否则返回假
int atomic_inc_and_test(atomic_t *v)原子变量v加 1,如果结果为0就返回真,否则返回假
int atomic_add_negative(int i, atomic_t *v)原子变量v加 i,如果结果为负就返回真,否则返回假

图表20- 1

至此,对于整型原子操作的相关API函数就讲解完成了,会在下一小节中使用上述原子整形操作API进行相应的实验。

下面对原子位操作进行讲解,和原子整形变量不同,原子位操作没有 atomic_t 的数据结构,原子位操作是直接对内存进行操作,原子位操作相关API函数如下(图表20-2)所示:

函数描述
void set_bit(int nr, void *p)将 p 地址的第 nr 位置 1。
void clear_bit(int nr,void *p)将 p 地址的第 nr 位清零。
void change_bit(int nr, void *p)将 p 地址的第 nr 位进行翻转。
int test_bit(int nr, void *p)获取 p 地址的第 nr 位的值。
int test_and_set_bit(int nr, void *p)将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值。
int test_and_clear_bit(int nr, void *p)将 p 地址的第 nr 位清零,并且返回 nr 位原来的值。
int test_and_change_bit(int nr, void *p)将 p 地址的第 nr 位翻转,并且返回 nr 位原来的值。

图表20- 2

对于原子位操作的知识就不再深入讲解和实验,感兴趣的同学可以到相关网站上进行自主学习。

在下一小节中,将会使用原子整形操作对19章的并发与竞争实验进行改进。

20.2 实验程序的编写

20.2.1 驱动程序编写

本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\15\module。

为了解决第19章实验中并发与竞争的问题,本章节实验将加入原子整形操作相关实验代码,在open()函数和release()函数中加入原子整形变量v的赋值代码,并且在open()函数中加入原子整形变量v的判断代码,从而实现同一时间内只允许一个应用打开该设备节点,以此来防止共享资源竞争的产生。

编写完成的atomic.c代码如下所示

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/errno.h>static atomic64_t v = ATOMIC_INIT(1);//初始化原子类型变量v,并设置为1
static int open_test(struct inode *inode,struct file *file)
{if(atomic64_read(&v) != 1){//读取原子类型变量v的值并判断是否等于1return -EBUSY;}atomic64_set(&v,0);//将原子类型变量v的值设置为0//printk("\nthis is open_test \n");return 0;
}static ssize_t read_test(struct file *file,char __user *ubuf,size_t len,loff_t *off)
{int ret;char kbuf[10] = "topeet";//定义char类型字符串变量kbufprintk("\nthis is read_test \n");ret = copy_to_user(ubuf,kbuf,strlen(kbuf));//使用copy_to_user接收用户空间传递的数据if (ret != 0){printk("copy_to_user is error \n");}printk("copy_to_user is ok \n");return 0;
}
static char kbuf[10] = {0};//定义char类型字符串全局变量kbuf
static ssize_t write_test(struct file *file,const char __user *ubuf,size_t len,loff_t *off)
{int ret;ret = copy_from_user(kbuf,ubuf,len);//使用copy_from_user接收用户空间传递的数据if (ret != 0){printk("copy_from_user is error\n");}if(strcmp(kbuf,"topeet") == 0 ){//如果传递的kbuf是topeet就睡眠四秒钟ssleep(4);}else if(strcmp(kbuf,"itop") == 0){//如果传递的kbuf是itop就睡眠两秒钟ssleep(2);}printk("copy_from_user buf is %s \n",kbuf);return 0;
}
static int release_test(struct inode *inode,struct file *file)
{//printk("\nthis is release_test \n");atomic64_set(&v,1);//将原子类型变量v的值赋1return 0;
}struct chrdev_test {dev_t dev_num;//定义dev_t类型变量dev_num来表示设备号int major,minor;//定义int类型的主设备号major和次设备号minorstruct cdev cdev_test;//定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类
};
struct chrdev_test dev1;//创建chrdev_test类型的
struct file_operations fops_test = {.owner = THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块.open = open_test,//将open字段指向open_test(...)函数.read = read_test,//将read字段指向read_test(...)函数.write = write_test,//将write字段指向write_test(...)函数.release = release_test,//将release字段指向release_test(...)函数
};static int __init atomic_init(void)
{if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自动获取设备号,设备名chrdev_nameprintk("alloc_chrdev_region is error \n");}printk("alloc_chrdev_region is ok \n");dev1.major = MAJOR(dev1.dev_num);//使用MAJOR()函数获取主设备号dev1.minor = MINOR(dev1.dev_num);//使用MINOR()函数获取次设备号printk("major is %d,minor is %d\n",dev1.major,dev1.minor);cdev_init(&dev1.cdev_test,&fops_test);//使用cdev_init()函数初始化cdev_test结构体,并链接到fops_test结构体dev1.cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用cdev_add()函数进行字符设备的添加dev1.class_test = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_testdevice_create(dev1.class_test,0,dev1.dev_num,0,"device_test");//使用device_create进行设备的创建,设备名称为device_testreturn 0;
}static void __exit atomic_exit(void)
{device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备class_destroy(dev1.class_test);//删除创建的类cdev_del(&dev1.cdev_test);//删除添加的字符设备cdev_testunregister_chrdev_region(dev1.dev_num,1);//释放字符设备所申请的设备号printk("module exit \n");
}
module_init(atomic_init);
module_exit(atomic_exit)
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");

20.2.2 编写测试 APP

本实验应用程序对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\15\app。

本测试app代码和上一章节相同,需要输入两个参数,第一个参数为对应的设备节点,第二个参数为“topeet”或者“itop”,分别代表向设备写入的数据,编写完成的应用程序app.c内容如下所示:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>#include <unistd.h>
int main(int argc, char *argv[])
{int fd;//定义int类型的文件描述符char str1[10] = {0};//定义读取缓冲区str1fd = open(argv[1],O_RDWR);//调用open函数,打开输入的第一个参数文件,权限为可读可写if(fd < 0 ){printf("file open failed \n");return -1;}/*如果第二个参数为topeet,条件成立,调用write函数,写入topeet*/    if (strcmp(argv[2],"topeet") == 0 ){write(fd,"topeet",10);}/*如果第二个参数为itop,条件成立,调用write函数,写入itop*/  else if (strcmp(argv[2],"itop") == 0 ){write(fd,"itop",10);}close(fd); return 0;
}

20.3 运行测试

20.3.1 编译驱动程序

在上一小节中的atomic.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += atomic.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:make -C $(KDIR) M=$(PWD) modules    #make操作
clean:make -C $(KDIR) M=$(PWD) clean    #make clean操作

对于Makefile的内容注释已在上图添加,保存退出之后,来到存放atomic.c和Makefile文件目录下,如下图(图20-4)所示:

img

图 20-4

然后使用命令“make”进行驱动的编译,编译完成如下图(图20-5)所示:

img

图 20-5

编译完生成atomic.ko目标文件,如下图(图20-6)所示:

img

图 20-6

至此驱动模块就编译成功了,下面进行应用程序的编译。

20.3.2 编译应用程序

来到应用程序app.c文件的存放路径如下图(图20-7)所示:

img

图 20-7

然后使用以下命令对app.c进行交叉编译,编译完成如下图(图20-8)所示:

aarch64-linux-gnu-gcc -o app app.c -static

img

图 20-8

生成的app文件就是之后放在开发板上运行的可执行文件,至此应用程序的编译就完成了。

20.3.3 运行测试

开发板启动之后,使用以下命令进行驱动模块的加载,如下图(图20-9)所示:

insmod atomic.ko

img

图 20-9

可以看到申请的主设备号和次设备号就被打印了出来,然后使用以下代码对自动生成的设备节点device_test进行查看,如下图(图20-10)所示:

 ls /dev/device_test

img

图 20-10

可以看到device_test节点已经被自动创建了,然后使用以下命令运行测试app,运行结果如下图(图20-11)所示:

./app /dev/device_test topeet

img

图 20-11

可以看到传递的buf值为topeet,然后输入以下命令在后台运行两个app,来进行竞争测试,运行结果如下图(图20-12)所示:

./app /dev/device_test topeet &./app /dev/device_test itop 

img

图 20-12

​ 可以看到应用程序在打开第二次/dev/device_test 文件的时候,出现了“file open failed”打印,证明文件打开失败,只有在第一个应用关闭相应的文件之后,下一个应用才能打开,通过限制同一时间内设备访问数量,来对共享资源进行保护。

最后可以使用以下命令进行驱动的卸载,如下图(图20-13)所示:

rmmod flag.ko

img

图 20-13

p /dev/device_test itop


[外链图片转存中...(img-ggVvfwTR-1694222528726)] 图 20-12​	可以看到应用程序在打开第二次/dev/device_test 文件的时候,出现了“file open failed”打印,证明文件打开失败,只有在第一个应用关闭相应的文件之后,下一个应用才能打开,通过限制同一时间内设备访问数量,来对共享资源进行保护。最后可以使用以下命令进行驱动的卸载,如下图(图20-13)所示:

rmmod flag.ko


[外链图片转存中...(img-hcalLUxj-1694222528726)] 图 20-13至此,原子操作实验就完成了。

相关文章:

第20章 原子操作实验(iTOP-RK3568开发板驱动开发指南 )

在上一章节的实验中&#xff0c;对并发与竞争进行了实验&#xff0c;两个app应用程序之间对共享资源的竞争访问引起了数据传输错误&#xff0c;而在Linux内核中&#xff0c;提供了四种处理并发与竞争的常见方法&#xff0c;分别是原子操作、自旋锁、信号量、互斥体&#xff0c;…...

Android 开机自启动

APP需要开机自启动&#xff0c;要通过开机广播实现。 1&#xff0c;在AndroidManifest.xml中增加权限 <!-- .接收启动完成的广播权限 --><uses-permission android:name"android.permission.RECEIVE_BOOT_COMPLETED" /> 2&#xff0c;在AndroidManifes…...

01_前端css编写的三种方式

前言 CSS的引入方式共有三种&#xff1a;行内样式、内部样式表、外部样式表 一、内联式引入 用法&#xff1a; 在元素上直接通过style属性进行设置css样式设置 示例&#xff1a; <h1 style"color:red;">style属性的应用</h1> <p style"font-si…...

07-垃圾收集算法详解

上一篇&#xff1a;06-JVM对象内存回收机制深度剖析 1.分代收集理论 当前虚拟机的垃圾收集都采用分代收集算法&#xff0c;这种算法没有什么新的思想&#xff0c;只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代&#xff0c;这样我们就可以根据各…...

Redis高并发分布式锁实战

高并发场景秒杀抢购超卖bug实战重现 秒杀抢购场景下实战JVM级别锁与分布式锁 大厂分布式锁Resisson框架实战 Lua脚本语言快速入门与使用注意事项 Redisson分布式锁源码剖析 Redis主从架构锁失效问题解析 从CAP角度剖析Redis与Zookeeper分布式锁区别 Redlock分布式锁原理与…...

MybatisPlus分页插件使用

一. 效果展示 二. 代码编写 2.1 pom <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version> </dependency>2.2 添加配置类 Configuration MapperScan(…...

Linux指令二【进程,权限,文件】

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程&#xff0c;是操作系统进行 资源分配和调度的一个独立单位&#xff0c;是应用程序运行的载体。 一、进程基本指令 1.ps&#xff1a;当前的用户进程 ps 只显示隶属于自己的进程状态ps -aux 显示所有进程…...

uni-app运行到微信开发者工具-没有打印的情况

前言 到我们进场使用微信开发者工具时&#xff0c;就会发现它经常会有bug&#xff0c;特别是在软件更新&#xff0c;组件库更新之后 最近在更新微信开发者工具之后发现所有打印都不显示了&#xff0c;虽然是小问题-但对于强迫症很烦 以为是代码配置问题-结果是更新之后打印开…...

由前端接口入门学习后端的controller层

由前端接口入门学习后端的controller层 一、简单介绍一下controller层&#xff1a;二、前端调用后端接口时&#xff0c;一般会传递参数给后端&#xff0c;后端的控制层是如何接收的呢&#xff1f;三、更深入地介绍一下关于请求体参数DTO作为入参Map作为入参 本文是以一个前端工…...

HJ71 字符串通配符

Powered by:NEFU AB-IN Link 文章目录 HJ71 字符串通配符题意思路代码 HJ71 字符串通配符 题意 问题描述&#xff1a;在计算机中&#xff0c;通配符一种特殊语法&#xff0c;广泛应用于文件搜索、数据库、正则表达式等领域。现要求各位实现字符串通配符的算法。 要求&#xff…...

ffmpeg 开发笔记

参考&#xff1a; FFmpeg音视频处理 - 知乎 通过python实时生成音视频数据并通过ffmpeg推送和混流 - 知乎 直播常用 FFmpeg & ffplay 命令 - 知乎 音视频 FFMPEG 滤镜使用 - 知乎 官网&#xff1a; ffmpeg Documentation...

一种基于注意机制的快速、鲁棒的混合气体识别和浓度检测算法,配备了具有双损失函数的递归神经网络

A fast and robust mixture gases identification and concentration detection algorithm based on attention mechanism equipped recurrent neural network with double loss function 摘要 提出一个由注意力机制组成的电子鼻系统。首先采用端到端的编码器译码器&#xff…...

[运维|系统] go程序设置开机启动踩坑笔记

参考文献 记systemctl启动go程序 在Ubuntu上作为systemctl服务运行时Go找不到文件 go语言程序设置开机启动&#xff0c;配置不生效 需要在服务配置文件中加入工作目录配置&#xff0c;示例 WorkingDirectory/path/to/go/program/directory...

CRC原理介绍及STM32 CRC外设的使用

1. CRC简介 循环冗余校验&#xff08;英语&#xff1a;Cyclic redundancy check&#xff0c;简称CRC&#xff09;&#xff0c;由 W. Wesley Peterson 于 1961 年首次提出的一种纠错码理论。 CRC是一种数据纠错方法&#xff0c;主要应用于数据通信或者数据存储的场合&#xff…...

Python 操作 Word

上次给大家介绍了 Python 如何操作 Excel &#xff0c;是不是感觉还挺有趣的&#xff0c;今天为大家再介绍下&#xff0c;用 Python 如何操作 Word &#xff0c;这个可能跟数据处理关系不大&#xff0c;用的也不多&#xff0c;不过可以先了解下都能实现什么功能&#xff0c;以备…...

Linux--进程创建(fork)-退出--孤儿进程

进程创建&#xff1a; ①使用fork函数创建一个进程&#xff0c;创建的新进程被称为子进程。 #include <unistd.h>//头文件 pid_t fork(void); fork函数调用成功&#xff0c;返回两次&#xff1a; 返回值为0&#xff0c; 代表当前进程为子进程&#xff1b; 返回值为非负数…...

LeetCode 热题 HOT 100:链表专题

LeetCode 热题 HOT 100&#xff1a;https://leetcode.cn/problem-list/2cktkvj/ 文章目录 2. 两数相加19. 删除链表的倒数第 N 个结点21. 合并两个有序链表23. 合并 K 个升序链表141. 环形链表142. 环形链表 II148. 排序链表160. 相交链表206. 反转链表234. 回文链表 2. 两数相…...

Redis发布订阅

在现代的软件开发中&#xff0c;数据存储和管理是至关重要的一环。Redis&#xff0c;作为一个开源的、内存中的数据结构存储系统&#xff0c;以其出色的性能和灵活的数据结构&#xff0c;赢得了开发者们的广泛喜爱。它不仅可以用作数据库&#xff0c;还可以用作缓存和消息代理。…...

在Windows操作系统上安装PostgreSQL数据库

在Windows操作系统上安装PostgreSQL数据库 一、在Windows操作系统上安装PostgreSQL数据库 一、在Windows操作系统上安装PostgreSQL数据库 点击 PostgreSQL可跳转至PostGreSQL的官方下载地址。 &#xff08;1&#xff09; &#xff08;2&#xff09;选择安装的目录&#xff…...

【云原生】Kubeadmin部署Kubernetes集群

目录 ​编辑 一、环境准备 1.2调整内核参数 二、所有节点部署docker 三、所有节点安装kubeadm&#xff0c;kubelet和kubectl 3.1定义kubernetes源 3.2开机自启kubelet 四、部署K8S集群 4.1查看初始化需要的镜像 4.2在 master 节点上传 v1.20.11.zip 压缩包至 /opt 目录…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

k8s业务程序联调工具-KtConnect

概述 原理 工具作用是建立了一个从本地到集群的单向VPN&#xff0c;根据VPN原理&#xff0c;打通两个内网必然需要借助一个公共中继节点&#xff0c;ktconnect工具巧妙的利用k8s原生的portforward能力&#xff0c;简化了建立连接的过程&#xff0c;apiserver间接起到了中继节…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用

文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么&#xff1f;1.1.2 感知机的工作原理 1.2 感知机的简单应用&#xff1a;基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...