第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)所示:
图 20-4
然后使用命令“make”进行驱动的编译,编译完成如下图(图20-5)所示:
图 20-5
编译完生成atomic.ko目标文件,如下图(图20-6)所示:
图 20-6
至此驱动模块就编译成功了,下面进行应用程序的编译。
20.3.2 编译应用程序
来到应用程序app.c文件的存放路径如下图(图20-7)所示:
图 20-7
然后使用以下命令对app.c进行交叉编译,编译完成如下图(图20-8)所示:
aarch64-linux-gnu-gcc -o app app.c -static
图 20-8
生成的app文件就是之后放在开发板上运行的可执行文件,至此应用程序的编译就完成了。
20.3.3 运行测试
开发板启动之后,使用以下命令进行驱动模块的加载,如下图(图20-9)所示:
insmod atomic.ko
图 20-9
可以看到申请的主设备号和次设备号就被打印了出来,然后使用以下代码对自动生成的设备节点device_test进行查看,如下图(图20-10)所示:
ls /dev/device_test
图 20-10
可以看到device_test节点已经被自动创建了,然后使用以下命令运行测试app,运行结果如下图(图20-11)所示:
./app /dev/device_test topeet
图 20-11
可以看到传递的buf值为topeet,然后输入以下命令在后台运行两个app,来进行竞争测试,运行结果如下图(图20-12)所示:
./app /dev/device_test topeet &./app /dev/device_test itop
图 20-12
可以看到应用程序在打开第二次/dev/device_test 文件的时候,出现了“file open failed”打印,证明文件打开失败,只有在第一个应用关闭相应的文件之后,下一个应用才能打开,通过限制同一时间内设备访问数量,来对共享资源进行保护。
最后可以使用以下命令进行驱动的卸载,如下图(图20-13)所示:
rmmod flag.ko
图 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开发板驱动开发指南 )
在上一章节的实验中,对并发与竞争进行了实验,两个app应用程序之间对共享资源的竞争访问引起了数据传输错误,而在Linux内核中,提供了四种处理并发与竞争的常见方法,分别是原子操作、自旋锁、信号量、互斥体,…...
Android 开机自启动
APP需要开机自启动,要通过开机广播实现。 1,在AndroidManifest.xml中增加权限 <!-- .接收启动完成的广播权限 --><uses-permission android:name"android.permission.RECEIVE_BOOT_COMPLETED" /> 2,在AndroidManifes…...
01_前端css编写的三种方式
前言 CSS的引入方式共有三种:行内样式、内部样式表、外部样式表 一、内联式引入 用法: 在元素上直接通过style属性进行设置css样式设置 示例: <h1 style"color:red;">style属性的应用</h1> <p style"font-si…...
07-垃圾收集算法详解
上一篇:06-JVM对象内存回收机制深度剖析 1.分代收集理论 当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各…...
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指令二【进程,权限,文件】
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行 资源分配和调度的一个独立单位,是应用程序运行的载体。 一、进程基本指令 1.ps:当前的用户进程 ps 只显示隶属于自己的进程状态ps -aux 显示所有进程…...
uni-app运行到微信开发者工具-没有打印的情况
前言 到我们进场使用微信开发者工具时,就会发现它经常会有bug,特别是在软件更新,组件库更新之后 最近在更新微信开发者工具之后发现所有打印都不显示了,虽然是小问题-但对于强迫症很烦 以为是代码配置问题-结果是更新之后打印开…...
由前端接口入门学习后端的controller层
由前端接口入门学习后端的controller层 一、简单介绍一下controller层:二、前端调用后端接口时,一般会传递参数给后端,后端的控制层是如何接收的呢?三、更深入地介绍一下关于请求体参数DTO作为入参Map作为入参 本文是以一个前端工…...
HJ71 字符串通配符
Powered by:NEFU AB-IN Link 文章目录 HJ71 字符串通配符题意思路代码 HJ71 字符串通配符 题意 问题描述:在计算机中,通配符一种特殊语法,广泛应用于文件搜索、数据库、正则表达式等领域。现要求各位实现字符串通配符的算法。 要求ÿ…...
ffmpeg 开发笔记
参考: FFmpeg音视频处理 - 知乎 通过python实时生成音视频数据并通过ffmpeg推送和混流 - 知乎 直播常用 FFmpeg & ffplay 命令 - 知乎 音视频 FFMPEG 滤镜使用 - 知乎 官网: 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 摘要 提出一个由注意力机制组成的电子鼻系统。首先采用端到端的编码器译码器ÿ…...
[运维|系统] go程序设置开机启动踩坑笔记
参考文献 记systemctl启动go程序 在Ubuntu上作为systemctl服务运行时Go找不到文件 go语言程序设置开机启动,配置不生效 需要在服务配置文件中加入工作目录配置,示例 WorkingDirectory/path/to/go/program/directory...
CRC原理介绍及STM32 CRC外设的使用
1. CRC简介 循环冗余校验(英语:Cyclic redundancy check,简称CRC),由 W. Wesley Peterson 于 1961 年首次提出的一种纠错码理论。 CRC是一种数据纠错方法,主要应用于数据通信或者数据存储的场合ÿ…...
Python 操作 Word
上次给大家介绍了 Python 如何操作 Excel ,是不是感觉还挺有趣的,今天为大家再介绍下,用 Python 如何操作 Word ,这个可能跟数据处理关系不大,用的也不多,不过可以先了解下都能实现什么功能,以备…...
Linux--进程创建(fork)-退出--孤儿进程
进程创建: ①使用fork函数创建一个进程,创建的新进程被称为子进程。 #include <unistd.h>//头文件 pid_t fork(void); fork函数调用成功,返回两次: 返回值为0, 代表当前进程为子进程; 返回值为非负数…...
LeetCode 热题 HOT 100:链表专题
LeetCode 热题 HOT 100:https://leetcode.cn/problem-list/2cktkvj/ 文章目录 2. 两数相加19. 删除链表的倒数第 N 个结点21. 合并两个有序链表23. 合并 K 个升序链表141. 环形链表142. 环形链表 II148. 排序链表160. 相交链表206. 反转链表234. 回文链表 2. 两数相…...
Redis发布订阅
在现代的软件开发中,数据存储和管理是至关重要的一环。Redis,作为一个开源的、内存中的数据结构存储系统,以其出色的性能和灵活的数据结构,赢得了开发者们的广泛喜爱。它不仅可以用作数据库,还可以用作缓存和消息代理。…...
在Windows操作系统上安装PostgreSQL数据库
在Windows操作系统上安装PostgreSQL数据库 一、在Windows操作系统上安装PostgreSQL数据库 一、在Windows操作系统上安装PostgreSQL数据库 点击 PostgreSQL可跳转至PostGreSQL的官方下载地址。 (1) (2)选择安装的目录ÿ…...
【云原生】Kubeadmin部署Kubernetes集群
目录 编辑 一、环境准备 1.2调整内核参数 二、所有节点部署docker 三、所有节点安装kubeadm,kubelet和kubectl 3.1定义kubernetes源 3.2开机自启kubelet 四、部署K8S集群 4.1查看初始化需要的镜像 4.2在 master 节点上传 v1.20.11.zip 压缩包至 /opt 目录…...
Java中wait和notify详解
线程的调度是无序的,随机的,但是也是有一定的需求场景,希望能够有序执行,join算是一种控制顺序的方式(功能有限)——》一个线程执行完,才能执行另一个线程! 本文主要讲解的…...
算法竞赛个人注意事项
浅浅记录一下自己在算法竞赛中的注意事项。 数据类 注意看数大小,数学库中的函数尽量加上 * 1.0,转成double,防止整型溢出。,int型相乘如果可能溢出,乘 * 1LL。 数据范围大于1e6,注意用快读。 浮点数输…...
ClickHouse和Doris超大数据集存储
文章目录 一. ClickHouse1. 性能2. 可靠性3. 可扩展性4. 支持SQL和复杂查询5. 适用场景 二. Doris1. 性能2. 可靠性3. 易用性4. 适用场景 三. ClickHouse和Doris的比较1. 架构2. 性能3. 可靠性4. 易用性5. 适用场景 四. 总结 ClickHouse和Doris是两种流行的超大数据集存储方案。…...
02-Flask-对象初始化参数
对象初始化参数 前言对象初始化参数import_namestatic_url_pathstatic_foldertemplate_floder 前言 本篇来学习Flask中对象初始化参数 对象初始化参数 import_name Flask程序所在的包(模块),传__name__就可以 _name_ 是一个标识 Python 模块的名字的变量&#x…...
第5篇 vue的通信框架axios和ui框架-element-ui以及node.js
一 axios的使用 1.1 介绍以及作用 axios是独立于vue的一个项目,基于promise用于浏览器和node.js的http客户端。 在浏览器中可以帮助我们完成 ajax请求的发送在node.js中可以向远程接口发送请求 1.2 案例使用axios实现前后端数据交互 1.后端代码 2.前端代码 &…...
RabbitMQ 知识点解读
1、AMQP 协议 1.1、AMQP 生产者的流转过程 当客户端与Broker 建立连接的时候,会调用factory .newConnection 方法,这个方法会进一步封装成Protocol Header 0-9-1 的报文头发送给Broker ,以此通知Broker 本次交互采用的是AMQPO-9-1 协议&…...
SimVODIS++: Neural Semantic Visual Odometry in Dynamic Environments 论文阅读
论文信息 题目:SimVODIS: Neural Semantic Visual Odometry in Dynamic Environments 作者:Ue-Hwan Kim , Se-Ho Kim , and Jong-Hwan Kim , Fellow, IEEE 时间:2022 来源: IEEE ROBOTICS AND AUTOMATION LETTERS(RAL…...
7.Xaml Image控件
1.运行图片 2.运行源码 a.xaml源码 <!--Source="/th.gif" 图像源--><!--Stretch="Fill" 填充模式--><Image x:Name...
Solidity 小白教程:11. 构造函数和修饰器
Solidity 小白教程:11. 构造函数和修饰器 这一讲,我们将用合约权限控制(Ownable)的例子介绍solidity语言中构造函数(constructor)和独有的修饰器(modifier)。 构造函数 构造函数&…...
静态工厂模式,抽象工厂模式,建造者模式
静态工厂模式 ublic class FruitFactory {public static Fruit getFruit(String name) {Fruit fnull;switch (name){case "APPLE":{fnew Apple();}case "BANANA":{fnew Banana();}default :{System.out.println("Unknown Fruit");}}return f;} …...
电影网站如何做seo排名/360上网安全导航
摘要 为了后续的深度学习的入门,平时看到的有关网站和视频,这里备份一下。 5个深度学习框架比较 分享一个关于最受欢迎的5个深度学习框架(SciKit Learn,TensorFlow,Theano,Keras,和Caffe&#x…...
三水网站制作/网站都有哪些
问题 在uniapp中使用 input 标签,希望修改 placeholder 属性内容的样式 <input placeholder"请输入内容" /> 在css( style)中修改未生效 input::input-placeholder {color: #bdbdbd; } /* 有些资料显示需要写,有些显示不需要ÿ…...
门户网站建设模板/大数据营销专业
有时很难看到网页上发布的图像上的内容。 好吧,借助于这些jQuery Zoom插件,用肉眼查看图像将不再那么困难。 高级–平滑缩放平移– jQuery图像查看器 这是一个基于javascript / CSS的图像查看器,可以显示自定义小区域内的产品照片,…...
黑客如何攻击网站/搜狗搜索旧版本
装上Oracle之后大家都会感觉到我们的电脑慢了下来,如何提高计算机的速度呢?我们应该打开必要的服务,关闭没有用的服务。下面是Oracle服务的详解: Oracle ORCL VSS Writer Service:Oracle卷映射拷贝写入服务,…...
泉州微信网站建设公司/百度首页推广
【What?】 Java DataBase Connectivity的缩写,简称为Java数据库连接。JDBC提供了一套Java中用于连接数据库的标准API。 【为什么会出现?】 SUN公司提供的一种数据库访问规则、规范,由于数据库种类较多,并且Java语言使用…...
那些网站可以做0首付分期手机/网络营销软文范文
讲解我们的爬虫之前,先概述关于爬虫的简单概念(毕竟是零基础教程) 爬虫 网络爬虫(又被称为网页蜘蛛,网络机器人)就是模拟浏览器发送网络请求,接收请求响应,一种按照一定的规则&…...