字符设备驱动开发
驱动就是获取外设、传感器数据和控制外设。数据会提交给应用程序。
Linux 驱动编译既要编写一个驱动,还要编写一个简单的测试应用程序。
而单片机下驱动和应用都是放在一个文件里,也就是杂在一块。而 Linux 则是分开了。
一、字符设备驱动开发流程
Linux 里一切皆文件,驱动设备表现就是一个/dev/下的文件,/dev/led。应用程序调用 open 函数 打开设备,比如 led。应用程序通过 write 函数向 /dev/led 写数据,比如写1打开,写0关闭。如果要关闭设备就是 close 函数。
字符设备驱动的编写主要是驱动对应的 open、close、read。其实就是 file_operations 结构体的成员变量的实现。
二、驱动模块的加载与卸载
Linux 驱动程序可以编译到 kernel 里,也就是 zImage。也可以编译成模块ko。测试的时候只需要加载ko即可。
1. 驱动编写
编写驱动的注意事项!
编译驱动的时候需要用到 linux 内核源码!因此需要解压缩 Linux 内核源码,编译 Linux 内核源码。得到 zImage 和 dtb。需要使用编译后得到的 zImage 和 dtb 启动系统。这部分不懂的回去看 Linux 内核移植部分。
先编写一个简单的源码,用于测试驱动。
#include <linux/module.h>
static int __init chrdevbase_init(void)
{return 0;
}static void __exit chrdevbase_exit(void)
{}/*模块入口与出口
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
Makefile 的编写
KERNELDIR := /home/prover/linux/linux_okCURRENT_PATH := $(shell pwd)obj-m := chrdevbase.obuild : kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
需要隐藏的文件
{"search.exclude": {"**/node_modules": true,"**/bower_components": true,"**/*.o":true,"**/*.su":true, "**/*.cmd":true,"Documentation":true, },"files.exclude": {"**/.git": true,"**/.svn": true,"**/.hg": true,"**/CVS": true,"**/.DS_Store": true, "**/*.o":true,"**/*.su":true, "**/*.cmd":true,"Documentation":true, }
}
指定内核源码路径
{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/prover/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include", "/home/prover/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include", "/home/prover/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"],"defines": [],"compilerPath": "/usr/bin/clang","cStandard": "c11","cppStandard": "c++17","intelliSenseMode": "clang-x64"}],"version": 4
}
编译后,.ko就是我们需要的驱动文件了。
2. 驱动模块的加载和卸载
开发板上使用命令 modprobe
发现需要创建/lib/modules。
将 .ko文件和可执行文件 chrdevbase.o 拷贝到该目录下
对于一个新的模块使用modprobe,需要先使用depmod命令,否则报下面错误:
如果报下面错误,说明内核和你驱动不是同源的。
成功后,还有个 license 的警告。
在源码中添加 License,还可以再加个作者。当然我们还在函数中添加了printk语句,用于观察:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>static int __init chrdevbase_init(void)
{printk("chrdevbase_init\r\n");return 0;
}static void __exit chrdevbase_exit(void)
{printk("chrdevbase_exit\r\n");
}MODULE_LICENSE("GPL");
MODULE_AUTHOR("Prover");
make编译后,再拷贝到指定目录下。然后modprobe加载驱动,最后再rmmod卸载驱动。
3. 字符设备注册与注销
对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模 块的时候也需要注销掉字符设备。
函数原型为:
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
register_chrdev 函数用于注册字符设备,此函数一共有三个参数,这三个参数的含义如下:
major:主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两 部分,关于设备号后面会详细讲解。
name:设备名字,指向一串字符串。
fops:结构体 file_operations 类型指针,指向设备的操作函数集合变量。
unregister_chrdev 函数用户注销字符设备,此函数有两个参数,这两个参数含义如下:
major:要注销的设备对应的主设备号。 name:要注销的设备对应的设备名。
先查看下存在的设备号,我们觉得设置为200。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>static struct file_operations test_fops;static int __init chrdevbase_init(void)
{//入口函数具体内容int retvalue = 0;//注册字符设备驱动retvalue = register_chrdev(200, "chrtest", &test_fops);if(retvalue < 0){//字符设备注册失败}//printk("chrdevbase_init\r\n");return 0;
}static void __exit chrdevbase_exit(void)
{unregister_chrdev(200, "chrtest");//printk("chrdevbase_exit\r\n");
}/*模块入口与出口
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Prover");
4. 实现设备的具体操作函数
file_operations 结构体就是设备的具体操作函数。
需要实现的基本功能:打开和关闭,读写。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>//打开设备
static int chrtest_open(struct inode* inode, struct file* filp)
{//用户实现具体功能
}//从设备读取
static ssize_t chrtest_read(struct file* filp, char __user* buf, size_t cnt, loff_t* offt)
{//用户实现具体功能return 0;
}//向设备写数据
static ssize_t chrtest_write(struct file* filp,const char __user* buf,size_t cnt, loss_t *offt)
{//用户实现具体功能return 0;
}//关闭/释放设备
static int chrtest_release(struct inode *inode, struct file* filp)
{//用户实现具体功能return 0;
}static struct file_operations test_fops = {.owner = THIS_MODULE,.open = chrtest_open,.read = chrtest_read,.write = chrtest_write,.release = chrtest_release,
};static int __init chrdevbase_init(void)
{//入口函数具体内容int retvalue = 0;//注册字符设备驱动retvalue = register_chrdev(200, "chrtest", &test_fops);if(retvalue < 0){//字符设备注册失败}//printk("chrdevbase_init\r\n");return 0;
}static void __exit chrdevbase_exit(void)
{unregister_chrdev(200, "chrtest");//printk("chrdevbase_exit\r\n");
}/*模块入口与出口
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Prover");
三、Linux 设备号
为了方便管理,Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分 组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。
Linux 提供了 一个名为 dev_t 的数据类型表示设备号,dev_t 定义在文件 include/linux/types.h 里面,定义如下:
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
可以看出 dev_t 是__u32 类型的,而__u32 定义在文件 include/uapi/asm-generic/int-ll64.h 里 面,定义如下:
typedef unsigned int __u32;
dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。其中高 12 位为主设备号,低 20 位为次设备号。
设备号操作函数:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
宏 MINORBITS 表示次设备号位数,一共是 20 位。
宏 MINORMASK 表示次设备号掩码。
宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。
前面自己分配的200这个设备号,其实算静态分配。当然也有提供动态分配设备号的方式,设备号的申请函数如下:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
dev:保存申请到的设备号。
baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这 些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递 增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count:要申请的设备号数量。
name:设备名字。
注销字符设备之后要释放掉设备号,设备号释放函数如下:
void unregister_chrdev_region(dev_t from, unsigned count)
from:要释放的设备号。
count:表示从 from 开始,要释放的设备号数量。
四、字符设备驱动开发实验
1. 完善驱动程序
第二节将驱动的框架写好了,接下来要完善设备号等一系列东西。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/types.h>#define CHRDEVBASE_MAJOR 200 //主设备号
#define CHRDEVBASE_NAME "chrdevbase" //设备名static char readbuf[100];
static char writebuf[100];
static char kerneldata[] = {"kernel data!"};/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int chrtest_open(struct inode* inode, struct file* filp)
{//用户实现具体功能return 0;
}/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t chrtest_read(struct file* filp, char __user* buf, size_t cnt, loff_t* offt)
{//用户实现具体功能int retvalue = 0;//向用户空间发送数据memcpy(readbuf, kerneldata, sizeof(kerneldata));retvalue = copy_to_user(buf, readbuf, cnt);if(retvalue == 0){printk("kernel senddata ok!\r\n");}else{printk("kernel senddata failed!\r\n");}return 0;
}/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t chrtest_write(struct file* filp,const char __user* buf,size_t cnt, loff_t *offt)
{//用户实现具体功能int retvalue = 0;//接收用户空间传递给内核的数据并且打印出来retvalue = copy_from_user(writebuf, buf, cnt);if(retvalue == 0){printk("kernel recevdata:%s\r\n",writebuf);}else{printk("kernel recevdata failed!\r\n");}return 0;
}/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int chrtest_release(struct inode *inode, struct file* filp)
{//用户实现具体功能return 0;
}/** 设备操作函数结构体 */
static struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,.open = chrtest_open,.read = chrtest_read,.write = chrtest_write,.release = chrtest_release,
};/*
* @description : 驱动入口函数
* @param : 无
* @return : 0 成功;其他 失败
*/
static int __init chrdevbase_init(void)
{//入口函数具体内容int retvalue = 0;//注册字符设备驱动retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);if(retvalue < 0){//字符设备注册失败printk("chrdevbase driver register failed\r\n");}printk("chrdevbase_init()\r\n");return 0;
}/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit chrdevbase_exit(void)
{unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);printk("chrdevbase_exit()\r\n");
}/*模块入口与出口
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);//LICENSE 和 作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Prover");
2. 编写测试APP
这部分,如果有 Linux C 编程的基础就更好了。调用一些 C 库文件操作基本函数。
chrdevbaseApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"/** 使用方法*./chrdevbaseApp /dev/chrdevbase <1>|<2>* argv[2] 1:读文件 * argv[2] 2:写文件
*/static char usrdata[] = {"usr data!"}; /*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{ int fd, retvalue; char *filename; char readbuf[100], writebuf[100]; if(argc != 3){ printf("Error Usage!\r\n"); return -1; } filename = argv[1]; /* 打开驱动文件 */ fd = open(filename, O_RDWR); if(fd < 0){ printf("Can't open file %s\r\n", filename); return -1; } if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */ retvalue = read(fd, readbuf, 50); if(retvalue < 0){ printf("read file %s failed!\r\n", filename); }else{ /* 读取成功,打印出读取成功的数据 */ printf("read data:%s\r\n",readbuf); } } if(atoi(argv[2]) == 2){ /* 向设备驱动写数据 */ memcpy(writebuf, usrdata, sizeof(usrdata)); retvalue = write(fd, writebuf, 50); if(retvalue < 0){ printf("write file %s failed!\r\n", filename); } } /* 关闭设备 */ retvalue = close(fd); if(retvalue < 0){ printf("Can't close file %s\r\n", filename); return -1; } return 0;
}
使用交叉编译器编译
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
3. 加载驱动模块
将驱动文件和App文件放入根文件的lib/modules/4.1.15下
sudo cp chrdevbase.ko chrdevbaseApp /home/prover/linux/nfs/rootfs/lib/modules/4.1.15/ -f
用modprobe驱动 .ko 后,查看设备号。
cat /proc/devices
当前系统存在 chrdevbase 这个设备,主设备号为 200,跟我们设置 的主设备号一致。
4. 创建设备节点文件
驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操 作这个设备节点文件来完成对具体设备的操作。输入如下命令创建/dev/chrdevbase 这个设备节 点文件:
mknod /dev/chrdevbase c 200 0
然后查看
5. 设备操作测试
./chrdevbaseApp /dev/chrdevbase 1
第一行是 chrdevbase_read 函数 输出的信息。第二行则是APP中输出的接收到的数据:kernel data!
刚才的 1 是读文件操作,现在输入 2 来实现写文件操作:
./chrdevbaseApp /dev/chrdevbase 2
既然读写都没问题,说明我们编写 的 chrdevbase 驱动是没有问题的。
6. 卸载驱动模块
不再使用某个设备的话,驱动卸载即可。
rmmod chrdevbase.ko
相关文章:

字符设备驱动开发
驱动就是获取外设、传感器数据和控制外设。数据会提交给应用程序。 Linux 驱动编译既要编写一个驱动,还要编写一个简单的测试应用程序。 而单片机下驱动和应用都是放在一个文件里,也就是杂在一块。而 Linux 则是分开了。 一、字符设备驱动开发流程 Lin…...

c语言:取绝对值
假设我们有一个 long 类型的变量 l,我们希望恢复其绝对值。以下是两种方法的对比: 方法1:使用条件语句 这个很好理解,负数时取负运算 ,用于数值的符号反转。 long abs_value(long l) {if (l < 0) {return -l;} e…...

DeepSeek从入门到精通教程PDF清华大学出版
DeepSeek爆火以来,各种应用方式层出不穷,对于很多人来说,还是特别模糊,有种雾里看花水中望月的感觉。 最近,清华大学新闻与传播学院新媒体研究中心,推出了一篇DeepSeek的使用教程,从最基础的是…...

HTML之CSS定位、浮动、盒子模型
HTML之CSS定位、浮动、盒子模型 定位 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document<…...

LQB(1)-python-各种基础排序
前言 除了内置的快速排序sort(),python也可以实现冒泡排序、选择排序、插入排序、快速排序、归并排序和桶排序。 一、冒泡排序 (Bubble Sort) 基础代码 def bubble_sort(arr):n len(arr)for i in range(n):swapped False # 优化:若本轮无交换则提前…...

解锁国内主流前端与后端框架
前端框架大揭秘 在当今的 Web 开发领域,前端框架的地位愈发举足轻重。随着用户对 Web 应用交互性和体验性要求的不断攀升,前端开发不再仅仅是简单的页面布局与样式设计,更需要构建复杂且高效的用户界面。前端框架就像是一位得力助手…...

使用OBS推流,srs服务器播放
说明: ffmpeg可以推流,但是是命令行方式不太友好,还可以使用主流的OBS开源推流软件,可从官网Open Broadcaster Software | OBS 下载最新版本,目前很多网络主播都是用它做直播。该软件支持本地视频文件以及摄像头推流。…...

【鸿蒙HarmonyOS Next实战开发】多媒体视频播放-ijkplayer
简介 ijkplayer是OpenHarmony和HarmonyOS环境下可用的一款基于FFmpeg的视频播放器。 演示 下载安装 ohpm install ohos/ijkplayer使用说明 import { IjkMediaPlayer } from "ohos/ijkplayer";import type { OnPreparedListener } from "ohos/ijkplayer";i…...

GRU 和 LSTM 公式推导与矩阵变换过程图解
GRU 和 LSTM 公式推导与矩阵变换过程图解 GRULSTM 本文的前置篇链接: 单向/双向,单层/多层RNN输入输出维度问题一次性解决 GRU GRU(Gate Recurrent Unit)是循环神经网络(RNN)的一种,可以解决RNN中不能长期…...

现在中国三大运营商各自使用的哪些band频段
现在中国三大运营商4G和5G频段分配情况: 中国移动 4G频段: TD-LTE: Band 39:1880-1920MHz,实际使用1885-1915MHz。 Band 40:2300-2400MHz,实际使用2320-2370MHz。 Band 41:2515-26…...

使用Jenkins实现鸿蒙HAR应用的自动化构建打包
使用Jenkins实现鸿蒙HAR应用的自动化构建打包 在软件开发领域,自动化构建是提高开发效率和确保代码质量的重要手段。特别是在鸿蒙(OpenHarmony)应用开发中,自动化构建更是不可或缺。本文将详细介绍如何使用Jenkins命令行工具实现…...

AI时代,职场人如何开启学习之旅
为什么要学习 AI 在当今数字化时代,AI 正以前所未有的速度改变着我们的工作和生活方式。从智能客服到自动化生产,从数据分析到个性化推荐,AI 已经广泛渗透到各个行业和领域。学习 AI,对于工作人员来说,不仅是提升工作…...

MIT6.824 Lecture 2-RPC and Threads Lecture 3-GFS
Lecture 2-RPC and Threads Go语言在多线程、同步,还有很好用的RPC包 《Effective Go》 线程是实现并发的重要工具 在分布式系统里关注多线程的原因: I/O concurrencyParallelismConvenience Thread challenges 用锁解决race问题 Coordination channel…...

MySQL第五次作业
根据图片内容完成作业 1.建表 (1)建立两个表:goods(商品表)、orders(订单表) mysql> create table goods( -> gid char(8) primary key, -> name varchar(10), -> price decimal(8,2), -> num int); mysql> create t…...

【PDF提取内容】如何批量提取PDF里面的文字内容,把内容到处表格或者批量给PDF文件改名,基于C++的实现方案和步骤
以下分别介绍基于 C 批量提取 PDF 里文字内容并导出到表格,以及批量给 PDF 文件改名的实现方案、步骤和应用场景。 批量提取 PDF 文字内容并导出到表格 应用场景 文档数据整理:在处理大量学术论文、报告等 PDF 文档时,需要提取其中的关键信…...

智慧机房解决方案(文末联系,领取整套资料,可做论文)
智慧机房解决方案-软件部分 一、方案概述 本智慧机房解决方案旨在通过硬件设备与软件系统的深度整合,实现机房的智能化管理与服务,提升机房管理人员的工作效率,优化机房运营效率,确保机房设备的安全稳定运行。软件部分包括机房管…...

【C编程问题集中营】使用数组指针时容易踩得坑
【C编程问题集中营】使用数组指针时容易踩得坑 文章目录 【C编程问题集中营】使用数组指针时容易踩得坑一、获取数组首地址二、应用场景举例2.1 正常场景2.2 异常场景 三、总结 一、获取数组首地址 一维数组的首地址即数组第一个元素的指针,常用的获取一维数组首地…...

【Redis】Linux、Windows、Docker 环境下部署 Redis
一、Linux环境部署Redis 1、卸载 # 查看 Redis 是否还在运行 [appuserlocalhost redis]$ ps -ef|grep redis appuser 135694 125912 0 14:24 pts/1 00:00:00 ./bin/redis-server *:6379 appuser 135731 125912 0 14:24 pts/1 00:00:00 grep --colorauto redis# 停止…...

反函数定义及其推导
文章目录 定义存在条件举例说明总结 反函数是数学中一种特殊的函数,用于“逆转”另一个函数的映射关系。 定义 设有一个函数 f : X → Y f: X \to Y f:X→Y。如果存在一个函数 g : Y → X g: Y \to X g:Y→X,使得对于所有 x ∈ X x \in X x∈X 和 y…...

2025.2.9机器学习笔记:PINN文献阅读
2025.2.9周报 文献阅读题目信息摘要Abstract创新点网络架构实验结论缺点以及后续展望 文献阅读 题目信息 题目: GPT-PINN:Generative Pre-Trained Physics-Informed Neural Networks toward non-intrusive Meta-learning of parametric PDEs期刊: Fini…...

Oracle数据连接 Dblink
拓展: oracle远程登陆数据库 1.oracle客户端或者服务端 2.修改你的电脑如下路径文件(服务器IP,服务器的数据库名,服务器的数据库端口号) c:\oracle\product\10.2.0\db_1\NETWORK\ADMIN\tnsnames.ora orcl_109 (DESCRIPTION …...

fetch请求总结,fastadmin中后台接口强制返回json数据
fetch请求 提交图片,只支持formData方式,这样会自动变为multiform方式,而且一般的post大多都可以用这样的方式来完成请求 const formData new FormData(); formData.append(file, fileInput.files[0]); formData.append(pid, id); formData.append(dc, 1);fetch(/api/common…...

基于STM32的智能鱼缸水质净化系统设计
🤞🤞大家好,这里是5132单片机毕设设计项目分享,今天给大家分享的是智能鱼缸水质净化系统。 目录 1、设计要求 2、系统功能 3、演示视频和实物 4、系统设计框图 5、软件设计流程图 6、原理图 7、主程序 8、总结 1、设计要求…...

JAVA安全—FastJson反序列化利用链跟踪autoType绕过
前言 FastJson这个漏洞我们之前讲过了,今天主要是对它的链条进行分析一下,明白链条的构造原理。 Java安全—log4j日志&FastJson序列化&JNDI注入_log4j漏洞-CSDN博客 漏洞版本 1.2.24及以下没有对序列化的类做校验,导致漏洞产生 1.2.25-1.2.41增加了黑名单限制,…...

格式化字符串漏洞(Format String Vulnerability)
格式化字符串漏洞(Format String Vulnerability)是程序中因不当处理格式化字符串参数而导致的一类安全漏洞,常被攻击者利用来读取内存数据、篡改程序执行流程,甚至执行任意代码。以下是对其原理、利用方式及防御措施的详细解析&am…...

C++--iomanip库
目录 1. 设置字段宽度:std::setw() 2. 设置浮点数精度:std::setprecision() 3. 设置填充字符:std::setfill() 4. 控制对齐方式:std::left 和 std::right,std::internal 5. 控制进制输出:std::hex、std…...

Redis 集群原理、主从复制和哨兵模式的详细讲解
引言:本文记录了博主在学习Redis的过程中的原理,了解为什么使用与怎么样使用 Redis 集群,在使用 Redis 集群时出现的主从复制和哨兵模式的相关知识。本文并不涉及Redis安装。 文章目录 一、简单介绍什么是 Redis二、为什么要使用 Redis 集群三…...

基于Java的远程视频会议系统(源码+系统+论文)
第一章 概述 1.1 本课题的研究背景 随着人们对视频和音频信息的需求愈来愈强烈,追求远距离的视音频的同步交互成为新的时尚。近些年来,依托计算机技术、通信技术和网络条件的发展,集音频、视频、图像、文字、数据为一体的多媒体信息ÿ…...

springboot 事务管理
在Spring Boot中,事务管理是通过Spring框架的事务管理模块来实现的。Spring提供了声明式事务管理和编程式事务管理两种方式。通常,我们使用声明式事务管理,因为它更简洁且易于维护。 1. 声明式事务管理 声明式事务管理是通过注解来实现的。…...

深度学习-神经机器翻译模型
以下为你介绍使用Python和深度学习框架Keras(基于TensorFlow后端)实现一个简单的神经机器翻译模型的详细步骤和代码示例,该示例主要处理英 - 法翻译任务。 1. 安装必要的库 首先,确保你已经安装了以下库: pip insta…...