71. 我的第一个Linux驱动实验

news/2025/4/1 0:56:26/

一、字符设备驱动框架

字符设备驱动的编写主要就是驱动对应的open、close、read。。。其实就是
file_operations结构体的成员变量的实现。
其中关于 C 库以及如何通过系统调用“陷入” 到内核空间这个我们不用去管,我们重点关注的是应用程序和具体的驱动,应用程序使用到的函数在具体驱动程序中都有与之对应的函数,比如应用程序中调用了 open 这个函数,那么在驱动程序中也得有一个名为 open 的函数。每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合,内容如下所示

二、驱动模块的加载与卸载

Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用“insmod”命令加载驱动模块。在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。总之,将驱动编译为模块最大的好处就是方便开发,当驱动开发完成,确定没有问题以后就可以将驱动编译进Linux 内核中,当然也可以不编译进 Linux 内核中,具体看自己的需求。
模块有加载和卸载两种操作,我们在编写驱动的时候需要注册这两种操作函数,模块的加载和卸载注册函数如下:
在这里插入图片描述

module_init 函数用来向 Linux 内核注册一个模块加载函数,参数 xxx_init 就是需要注册的具体函数,当使用“insmod”命令加载驱动的时候, xxx_init 这个函数就会被调用。 module_exit()函数用来向 Linux 内核注册一个模块卸载函数,参数 xxx_exit 就是需要注册的具体函数,当使用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用。字符设备驱动模块加载和卸载模板如下所示:
在这里插入图片描述

Linux驱动程序可以编译到kernel里面,也就是zImage,也可以编译为模块,.ko。测试的时候只需要加载.ko模块就可以。

编写驱动的时候注意事项!
1、编译驱动的时候需要用到linux内核源码!因此要解压缩linux内核源码,编译linux内核源码。得到zImage和.dtb。需要使用编译后的到的zImage和dtb启动系统。
2、从SD卡启动,SD卡烧写了uboot。uboot通过tftp从ubuntu里面获取zimage和dtb,rootfs也是通过nfs挂在。
3、设置bootcmd和bootargs

bootargs=console=ttymxc0,115200 rw root=/dev/nfs nfsroot=192.168.1.66:/home/zzk/linux/nfs/rootfs ip=192.168.1.50:192.168.1.66:192.168.1.1:255.255.255.0::eth0:off
bootcmd=tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000;

4、将编译出来的.ko文件放到根文件系统里面。加载驱动会用到加载命令:insmod,modprobe。移除驱动使用命令rmmod。对于一个新的模块使用modprobe加载的时候需要先调用一下depmod命令。
驱动编译完成以后扩展名为.ko,有两种命令可以加载驱动模块: insmod和 modprobe, insmod是最简单的模块加载命令,此命令用于加载指定的.ko 模块,比如加载 drv.ko 这个驱动模块,命令如下:

insmod drv.ko

insmod 命令不能解决模块的依赖关系,比如 drv.ko 依赖 first.ko 这个模块,就必须先使用insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。 但是 modprobe 就不会存在这个问题, modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,因此modprobe 命令相比 insmod 要智能一些。 modprobe 命令主要智能在提供了模块的依赖性分析、错误检查、错误报告等功能,推荐使用 modprobe 命令来加载驱动。 modprobe 命令默认会去/lib/modules/目录中查找模块,比如本书使用的 Linux kernel 的版本号为 4.1.15,因此 modprobe 命令默认会到/lib/modules/4.1.15 这个目录中查找相应的驱动模块,一般自己制作的根文件系统中是不会有这个目录的,所以需要自己手动创建。

/lib/modules/4.1.15
/lib/modules/4.1.15 # modprobe chrdevbase
chrdevbase init!

驱动模块的卸载使用命令“rmmod”即可,比如要卸载 drv.ko,使用如下命令即可: rmmod drv.ko
也可以使用“modprobe -r”命令卸载驱动,比如要卸载 drv.ko,命令如下: modprobe -r drv.ko
使用 modprobe 命令可以卸载掉驱动模块所依赖的其他模块,前提是这些依赖模块已经没有被其他模块所使用,否则就不能使用 modprobe 来卸载驱动模块。所以对于模块的卸载,还是推荐使用 rmmod 命令。
5,驱动模块加载成功以后可以使用lsmod查看一下。
6,卸载模块使用rmmod命令

/lib/modules/4.1.15 # rmmod chrdevbase.ko 
chrdevbase exit!
/lib/modules/4.1.15 # lsmod
chrdevbase 1884 0 - Live 0x7f00c000 (O)

当应用程序调用 open 函数的时候此函数就会调用,本例程中我们没有做任何工作,只是输出一串字符,用于调试。这里使用了 printk 来输出信息,而不是 printf!因为在 Linux 内核中没有 printf 这个函数。 printk 相当于 printf 的孪生兄妹, printf运行在用户态, printk 运行在内核态。在内核中想要向控制台输出或显示一些内容,必须使用printk 这个函数。不同之处在于, printk 可以根据日志级别对消息进行分类,一共有 8 个消息级别,这 8 个消息级别定义在文件 include/linux/kern_levels.h 里面,定义如下:

三、字符设备的注册与注销

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。字符设备的注册和注销函数原型如下所示:
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: 要注销的设备对应的设备名。
一般字符设备的注册在驱动模块的入口函数 xxx_init 中进行,字符设备的注销在驱动模块的出口函数 xxx_exit 中进行。在示例代码 40.2.1.1 中字符设备的注册和注销,内容如下所示

1、我们需要向系统注册一个字符设备,使用函数register_chrdev。
2、卸载驱动的时候需要注销掉前面注册的字符设备,使用函数unregister_chrdev,注销字符设备。

四、设备号

1,Linux内核使用dev_t。

typedef __kernel_dev_t dev_t;
typedef __u32 __kernel_dev_t;
typedef unsigned int __u32;
2、Linux内核将设备号分为两部分:主设备号和次设备号。主设备号占用前12位,次设备号占用低20位。
因此 Linux系统中主设备号范围为 0~4095,所以大家在选择主设备号的时候一定不要超过这个范围。
在文件 include/linux/kdev_t.h 中提供了几个关于设备号的操作函数(本质是宏),如下所示
3、设备号的操作函数,或宏
从dev_t获取主设备号和次设备号,MAJOR(dev_t),MINOR(dev_t)。也可以使用主设备号和次设备号构成dev_t,通过MKDEV(major,minor)
示例代码 40.3.3 设备号操作函数
在这里插入图片描述

第 6 行,宏 MINORBITS 表示次设备号位数,一共是 20 位。第 7 行,宏 MINORMASK 表示次设备号掩码。
第 9 行,宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
第 10 行,宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
第 11 行,宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。

/lib/modules/4.1.15 # cat /proc/devices
Character devices:1 mem4 /dev/vc/04 tty5 /dev/tty5 /dev/console5 /dev/ptmx7 vcs10 misc13 input29 fb81 video4linux89 i2c90 mtd
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
200 chrdevbase
207 ttymxc
226 drm
250 ttyLP
251 watchdog
252 ptp
253 pps
254 rtcBlock devices:1 ramdisk
259 blkext7 loop8 sd31 mtdblock65 sd66 sd67 sd68 sd69 sd70 sd71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
/lib/modules/4.1.15 # 

五、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 (*iterate) (struct file *, struct dir_context *);
unsigned int (*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 *);
int (*mremap)(struct file *, struct vm_area_struct *);
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 (*aio_fsync) (struct kiocb *, 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
};

六、字符设备驱动框架的搭建

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
/***************************************************************
文件名     : chrdevbase.c
描述      : chrdevbase驱动文件。
***************************************************************/#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 chrdevbase_open(struct inode *inode, struct file *filp)
{//printk("chrdevbase open!\r\n");return 0;
}/** @description     : 从设备读取数据 * @param - filp    : 要打开的设备文件(文件描述符)* @param - buf     : 返回给用户空间的数据缓冲区* @param - cnt     : 要读取的数据长度* @param - offt    : 相对于文件首地址的偏移* @return          : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t chrdevbase_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");}//printk("chrdevbase read!\r\n");return 0;
}/** @description     : 向设备写数据 * @param - filp    : 设备文件,表示打开的文件描述符* @param - buf     : 要写给设备写入的数据* @param - cnt     : 要写入的数据长度* @param - offt    : 相对于文件首地址的偏移* @return          : 写入的字节数,如果为负值,表示写入失败*/
static ssize_t chrdevbase_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");}//printk("chrdevbase write!\r\n");return 0;
}/** @description     : 关闭/释放设备* @param - filp    : 要关闭的设备文件(文件描述符)* @return          : 0 成功;其他 失败*/
static int chrdevbase_release(struct inode *inode, struct file *filp)
{//printk("chrdevbase release!\r\n");return 0;
}/** 设备操作函数结构体*/
static struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,   .open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_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("mmk");

七、应用程序编写

Linux下一切皆文件,首先要open

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
文件名     : chrdevbaseApp.c
版本      : V1.0
描述      : chrdevbase驱测试APP。
其他      : 使用方法:./chrdevbase /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;
}

测试 APP 比较简单,只有一个文件,因此就不需要编写 Makefile 了,直接输入命令编译。因为测试 APP 是要在 ARM 开发板上运行的,所以需要使用 arm-linux-gnueabihf-gcc 来编译,输入如下命令:

arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp 

编译完成以后会生成一个叫做 chrdevbaseApp 的可执行程序,输入如下命令查看chrdevbaseAPP 这个程序的文件信息

file chrdevbaseApp
chrdevbaseApp: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.31, BuildID[sha1]=5d017375992cf6c40e8fccb19a238dfd552c

chrdevbaseAPP 这个可执行文件是 32 位 LSB 格式, ARM 版本的,因此 chrdevbaseAPP 只能在 ARM 芯片下运行。
拷 贝 完 成 以 后 就 会 在 开 发 板 的 /lib/modules/4.1.15 目 录 下 存 在 chrdevbase.ko 和chrdevbaseAPP 这两个文件
在这里插入图片描述

八、测 试

1、加载驱动。

modprobe chrdevbase.ko

或者

modprobe chrdevbase

2,设备驱动在dev里,进入/dev查看设备文件,chrdevbase。/dev/chrdevbase。但是实际没有,因为我们没有创建设备节点。

mknod /dev/chrdevbase c 200 0
/dev # ls
autofs              ram14               tty37
bus                 ram15               tty38
console             ram2                tty39
cpu_dma_latency     ram3                tty4
dri                 ram4                tty40
fb0                 ram5                tty41
full                ram6                tty42
fuse                ram7                tty43
hwrng               ram8                tty44
i2c-0               ram9                tty45
i2c-1               random              tty46
input               rfkill              tty47
kmsg                rtc0                tty48
loop-control        snd                 tty49
loop0               tty                 tty5
loop1               tty0                tty50
loop2               tty1                tty51
loop3               tty10               tty52
loop4               tty11               tty53
loop5               tty12               tty54
loop6               tty13               tty55
loop7               tty14               tty56
mem                 tty15               tty57
memory_bandwidth    tty16               tty58
mmcblk0             tty17               tty59
mmcblk1             tty18               tty6
mmcblk1boot0        tty19               tty60
mmcblk1boot1        tty2                tty61
mmcblk1p1           tty20               tty62
mmcblk1p2           tty21               tty63
mmcblk1rpmb         tty22               tty7
network_latency     tty23               tty8
network_throughput  tty24               tty9
null                tty25               ttymxc0
pps0                tty26               ttymxc1
pps1                tty27               ubi_ctrl
ptmx                tty28               urandom
ptp0                tty29               vcs
ptp1                tty3                vcs1
pts                 tty30               vcsa
ram0                tty31               vcsa1
ram1                tty32               video0
ram10               tty33               watchdog
ram11               tty34               watchdog0
ram12               tty35               zero
ram13               tty36

创建设备节点文件
驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。输入如下命令创建/dev/chrdevbase 这个设备节点文件:
mknod /dev/chrdevbase c 200 0
其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个字符设备,“ 200”是设备的主设备号,“ 0”是设备的次设备号。创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用“ls /dev/chrdevbase -l”命令查看,
/dev # ls /dev/chrdevbase -l
crw-r–r-- 1 0 0 200, 0 Jan 1 01:34 /dev/chrdevbase
如果 chrdevbaseAPP 想要读写 chrdevbase 设备,直接对/dev/chrdevbase 进行读写操作即可。相当于/dev/chrdevbase 这个文件是 chrdevbase 设备在用户空间中的实现。前面一直说 Linux 下一切皆文件,包括设备也是文件,现在大家应该是有这个概念了吧?
3、测试
进入/lib/modules/4.1.15 #

./chrdevbaseApp /dev/chrdevbase

使用 chrdevbaseApp 软件操作 chrdevbase 这个设备,看看读写是否正常,首先进行读操作,输入如下命令

./chrdevbaseApp /dev/chrdevbase
/lib/modules/4.1.15 # cat /proc/devices
Character devices:1 mem4 /dev/vc/04 tty5 /dev/tty5 /dev/console5 /dev/ptmx7 vcs10 misc13 input29 fb81 video4linux89 i2c90 mtd
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
200 chrdevbase
207 ttymxc
226 drm
250 ttyLP
251 watchdog
252 ptp
253 pps
254 rtcBlock devices:1 ramdisk
259 blkext7 loop8 sd31 mtdblock65 sd66 sd67 sd68 sd69 sd70 sd71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
/lib/modules/4.1.15 # 

没有200

rmmod chrdevbase
cat /proc/devices
modprobe chrdevbase

有200 了

/lib/modules/4.1.15 # mknod /dev/chrdevbase c 200 0 
/lib/modules/4.1.15 # mknod /dev/chrdevbase c 200 0 
mknod: /dev/chrdevbase: File exists
./chrdevbaseApp /dev/chrdevbase
/lib/modules/4.1.15 # ./chrdevbaseApp /dev/chrdevbase
Error Usage!

/

lib/modules/4.1.15 # ./chrdevbaseApp /dev/chrdevbase 1
kernel senddata ok!
read data:kernel data!
/lib/modules/4.1.15 # ./chrdevbaseApp /dev/chrdevbase 2
kernel recevdata:usr data!

在这里插入图片描述

/lib/modules/4.1.15 # lsmod
chrdevbase 1884 0 - Live 0x7f004000 (O)

八、chrdevbase虚拟设备驱动的完善

要求:应用程序可以对驱动读写操作,读的话就是从驱动里面读取字符串,写的话就是应用向驱动写字符串。
1、chrdevbase_read驱动函数编写
驱动给应用传递数据的时候需要用到copy_to_user函数。

if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */retvalue = read(fd, readbuf, 50);if(retvalue < 0){printf("read file %s failed!\r\n", filename);}else{/*  读取成功,打印出读取成功的数据 */printf("App read data:%s\r\n",readbuf);}
}

//编译驱动

make

//编译App

arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
/lib/modules/4.1.15 # ./chrdevbaseApp /dev/chrdevbase 1
kernel senddata ok!
App read data:kernel data!

http://www.ppmy.cn/news/1584124.html

相关文章

正则表达式-万能表达式

1、正则 正则表达式是一组由字母和符号组成的特殊文本, 它可以用来从文本中找 出满足你想要的格式的句子. {“basketId”: 0, “count”: 1, “prodId”: #prodId#, “shopId”: 1, “skuId”: #skuId#} #prodId# re相关的文章&#xff1a; https://www.cnblogs.com/Simple-S…

Jmeter-负载测试

目录 一. 基础负载测试场景&#xff1a;固定并发用户数 1、线程组配置 2、HTTP请求配置 3、添加定时器 4、添加监听器 4.1 聚合报告 4.2 响应时间图 4.3 查看结果树 5、结果分析指标 二. 阶梯式加压场景&#xff08;逐步增加并发&#xff09; 1、插件安装 2、阶梯配…

利用GitHub Pages快速部署前端框架静态网页

文章目录 前言GitHub Pages 来部署前端框架&#xff08;Vue 3 Vite&#xff09;项目1、配置 GitHub Pages 部署2、将项目推送到 GitHub3、部署到 GitHub Pages4、访问部署页面5、修改代码后的更新部署顺序 前言 可以先参考&#xff1a; 使用 GitHub Pages 快速部署静态网页: …

Qt中QApplication::processEvents()详细讲解

QApplication::processEvents(); 是 Qt 框架中的一个关键函数&#xff0c;用于手动处理当前线程的事件队列。以下是其详细解释及使用场景&#xff1a; 1.作用 1.1处理挂起的事件 在默认情况下&#xff0c;Qt 的事件循环&#xff08;通过 QApplication::exec() 启动&#xff…

SpringBoot3解决跨域请求问题(同源策略、JSONP、CORS策略)(Access-Control-Allow-Origin)(2025详细教程)

目录 浏览器跨域请求问题。 浏览器同源策略。 第三方API调用。 前后端分离项目。 一、JSONP。&#xff08;dataType:jsonp&#xff09; &#xff08;1&#xff09;代码示例。 <1>前端ajax04.jsp页面。(发起Ajax请求) <2>后端springboot接口。(/hello)(返回JSONPObj…

“立正挨打”之后,黄仁勋正式公布英伟达的量子计算应对战略

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 文丨浪味仙 排版丨浪味仙 行业动向&#xff1a;4500字丨13分钟阅读 当 14 位量子计算企业高管聚集在同一个讨论会上&#xff0c;为各自公司的生存和技术选择辩护时&#xff0c;会发生什么&…

【软考备考】系统架构设计论文完整范文示例

本文由AI辅助创造 题目:基于微服务与云原生的智慧政务平台架构设计与实践 摘要(约300字) 本文以某省级智慧政务平台建设项目为背景,针对传统政务系统存在的"信息孤岛"、扩展性差、维护成本高等问题,提出了一套基于微服务与云原生技术的解决方案。通过领域驱动…

Flutter环境配置

配置环境变量 PUB_HOSTED_URLhttps://pub.flutter-io.cnFLUTTER_STORAGE_BASE_URLhttps://storage.flutter-io.cn 这个命令是用来配置 Flutter 的镜像源地址&#xff0c;主要是为了解决在中国大陆地区访问 Flutter 官方资源较慢的问题。 具体的操作如下&#xff1a; 右键点…