Linux驱动开发--阻塞、非阻塞I/O
2. 阻塞、非阻塞I/O
IO 指的是 Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。对于非阻塞 IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。
在阻塞访问时,不能获取资源的进程将进入休眠,它将CPU资源“礼让”给其他进程。因为阻塞的进程会进入休眠状态,所以必须确保有一个地方能够唤醒休眠的进程,否则,进程就真的“寿终正寝”了。唤醒进程的地方最大可能发生在中断里面,因为在硬件资源获得的同时往往伴随着一个中断。而非阻塞的进程则不断尝试,直到可以进行I/O.
对于设备驱动文件的默认读取方式就是阻塞式的,
// 代码清单 8.1 阻塞地读串口一个字符
char buf;
fd = open("/dev/ttyS1", O_RDWR);
...
res = read(fd, &buf, 1); /* 当串口上有输入时才返回 */
if(res == 1)printf("%c\n", buf);// 代码清单 8.2 非阻塞地读串口一个字符
char buf;
fd = open("/dev/ttyS1", O_RDWR | O_NONBLOCK);
...
while(read(fd, &buf, 1) != 1)continue; /* 串口上无输入也返回,因此要循环尝试读取串口 */
printf("%c\n", buf);
除了在打开文件时可以指定阻塞还是非阻塞方式以外,在文件打开后,也可以通过ioctl()
和fcntl()
改变读写的方式,如从阻塞变更为非阻塞或者从非阻塞变更为阻塞。例如,调用 fcntl(fd.F_SETFL,O_NONBLOCK)
可以设置 fd
对应的 I/0 为非阻塞。
2.1 阻塞–等待队列(休眠、唤醒)
在 Linux 驱动程序中,可以使用等待队列(Wait Queue)来实现阻塞进程的唤醒。等待队列很早就作为一个基本的功能单位出现在 Linux 内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,可以用来同步对系统资源的访问,第 7 章中所讲述的信号量在内核中也依赖等待队列来实现。
Linux 内核提供了如下关于等待队列的操作:
①定义并初始化“等待队列头部”
等待队列头使用结构体wait_queue_head_t 表示, wait_queue_head_t 结构体定义在文件 include/linux/wait.h 中,结构体内容如下所示:
struct __wait_queue_head {spinlock_t lock;struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
定义好等待队列头以后需要初始化, 使用 init_waitqueue_head
函数初始化等待队列头,函数原型如下:
extern void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *);#define init_waitqueue_head(q) \do { \static struct lock_class_key __key; \\__init_waitqueue_head((q), #q, &__key); \} while (0)
//参数 q 就是要初始化的等待队列头。
也可以使用宏 DECLARE_WAIT_QUEUE_HEAD
来一次性完成等待队列头的定义的初始化。
#define DECLARE_WAIT_QUEUE_HEAD(name) \wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \.lock = __SPIN_LOCK_UNLOCKED(name.lock), \.task_list = { &(name).task_list, &(name).task_list } }
②定义等待队列元素
等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。结构体 wait_queue_t
表示等待队列项,结构体内容如下:
typedef struct __wait_queue wait_queue_t;
struct __wait_queue {unsigned int flags;void *private;wait_queue_func_t func;struct list_head task_list;
};
使用宏 DECLARE_WAITQUEUE(name, tsk)
定义并初始化一个等待队列项,宏的内容如下: name就是等待队列项的名字, tsk 表示这个等待队列项属于哪个任务(进程),一般设置为current , 在 Linux 内 核 中 current 相 当 于 一 个 全 局 变 量 , 表 示 当 前 进 程 。 因 此 宏DECLARE_WAITQUEUE
就是给当前正在运行的进程创建并初始化了一个等待队列项。
#define DECLARE_WAITQUEUE(name, tsk) \wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)#define __WAITQUEUE_INITIALIZER(name, tsk) { \.private = tsk, \.func = default_wake_function, \.task_list = { NULL, NULL } }
③添加/移除等待队列
当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠态。当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可 。
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
add_wait_queue() 用于将等待队列元素 wait 添加到等待队列头部 q 指向的双向链表中,而 remove_wait_queue() 用于将等待队列元素 wait 从由 q 头部指向的链表中移除。下面简单看一下添加过程:
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{list_add(&new->task_list, &head->task_list);
}static inline void list_add(struct list_head *new, struct list_head *head)
{__list_add(new, head, head->next);
}static inline void __list_add(struct list_head *new,struct list_head *prev,struct list_head *next)
{//头插法next->prev = new;new->next = next;new->prev = prev;WRITE_ONCE(prev->next, new);
}
④唤醒队列
上述操作会唤醒以 queue 作为等待队列头部的队列中所有的进程。(但是现在应该改成上表中的状态了!!!)
-
wake_up() 应该与 wait_event() 或 wait_event_timeout() 成对使用;
-
wake_up_interruptible() 则应与 wait_event_interruptible() 或 wait_event_interruptible_timeout() 成对使用。
-
wake_up() 可唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 的进程,而 wake_up_interruptible() 只能唤醒处于 TASK_INTERRUPTIBLE 的进程。
⑤等待事件
除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程,和等待事件有关的 API 函数如下 所示:
等待第 1 个参数 queue 作为等待队列头部的队列被唤醒,而且第 2 个参数 condition 必须满足,否则继续阻塞。wait_event() 和 wait_event_interruptible() 的区别在于后者可以被信号打断,而前者不能。加上 _timeout 后的宏意味着阻塞等待的超时时间,以 jiffies 为单位,在第 3 个参数的 timeout 到达时,不论 condition 是否满足,均返回。
Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies 初始化为 0;在编译 Linux 内核的时候可以通过图形化界面设置系统节拍率 。比如 1000Hz, 100Hz 等等 。
⑦在等待队列上睡眠
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
sleep_on() 函数的作用就是将目前进程的状态置成 TASK_UNINTERRUPTIBLE,并定义一个等待队列元素,之后把它挂到等待队列头部 q 指向的双向链表,直到资源可获得,q 队列指向链接的进程被唤醒。
interruptible_sleep_on() 与 sleep_on() 函数类似,其作用是将目前进程的状态置成 TASK_INTERRUPTIBLE,并定义一个等待队列元素,之后把它附属到 q 指向的队列,直到资源可获得(q 指引的等待队列被唤醒)或者进程收到信号。
sleep_on() 函数应该与 wake_up() 成对使用,interruptible_sleep_on() 应该与 wake_up_interruptible() 成对使用。
再设备驱动中使用等待队列–模板:
static size_t xxx_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
{...DECLARE_WAITQUEUE(wait, current);/* 添加元素到等待队列 */add_wait_queue(&xxx_wait, &wait); /* 等待设备缓冲区可写 */do {avail = device_writable(...);if (avail < 0) {if (file->f_flags & O_NONBLOCK) { /* 非阻塞 */ret = -EAGAIN;goto out;}__set_current_state(TASK_INTERRUPTIBLE); /* 改变进程状态 */schedule(); /* 调度其他进程执行 */if (signal_pending(current)) { /* 如果是因为信号唤醒 */ret = -ERESTARTSYS;goto out;}}} while (avail < 0);/* 写设备缓冲区 */device_write(...)out:remove_wait_queue(&xxx_wait, &wait); /* 将元素移出 xxx_wait 指引的队列 */set_current_state(TASK_RUNNING); /* 设置进程状态为 TASK_RUNNING */return ret;
}
1)如果是非阻塞访问(O_NONBLOCK 被设置),设备忙时,直接返回“-EAGAIN”。
2)对于阻塞访问,会调用 __set_current_state(TASK_INTERRUPTIBLE) 进行进程状态切
换并显示通过“schedule()”调度其他进程执行。
3)醒来的时候要注意,由于调度出去的时候,进程状态是 TASK_INTERRUPTIBLE,
即浅度睡眠,所以唤醒它的有可能是信号,因此,我们首先通过 signal_pending(current) 了解
是不是信号唤醒的,如果是,立即返回“-ERESTARTSYS”。
DECLARE_WAITQUEUE
、add_wait_queue
这两个动作加起来完成的效果如图 8.2 所示。在 wait_queue_head_t
指向的链表上,新定义的 wait_queue
元素被插入,而这个新插入的元素绑定了一个 task_struct
(当前做 xxx_write 的 current,这也是DECLARE_WAITQUEUE
使用 “current
” 作为参数的原因)。
内核定义了 task_struct 结构体表示一个进程.
2.2 实验示例
2.3 非阻塞–轮询(POLL、Select机制)
如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。 poll
、 epoll
和 select
可以用于处理轮询,应用程序通过 select
、 epoll
或 poll
函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调用 select
、 epoll
或 poll
函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数。
2.3.1 select 函数
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout);
其中 readfds、writefds、exceptfds
分别是被 select() 监视的读、写和异常处理的文件描述符集合,numfds
的值是需要检查的号码最高的 fd
加 1。readfds
文件集中的任何一个文件变得可读,select() 返回;同理,writefds
文件集中的任何一个文件变得可写,select 也返回。
如图 8.3 所示,第一次对 n 个文件进行 select() 的时候,若任何一个文件满足要求,select() 就直接返回;第 2 次再进行 select() 的时候,没有文件满足读写要求,select() 的进程阻塞且睡眠。由于调用 select() 的时候,每个驱动的 poll() 接口都会被调用到,实际上执行select() 的进程被挂到了每个驱动的等待队列上,可以被任何一个驱动唤醒。如果 FD 变得可读写,select() 返回。
imeout
:超时时间,当我们调用 select 函数等待某些文件描述符可以设置超时时间,超时时间使用结构体 timeval 表示,结构体定义如下所示:
struct timeval {long tv_sec; /* 秒 */long tv_usec; /* 微妙 */
}
当 timeout 为 NULL 的时候就表示无限期的等待。 此时就是编程阻塞IO了!
返回值: 0,表示的话就表示超时发生,但是没有任何文件描述符可以进行操作; -1,发生错误;其他值,可以进行操作的文件描述符个数。
比如我们现在要从一个设备文件中读取数据,那么就可以定义一个 fd_set
变量,这个变量要传递给参数 readfds
。当我们定义好一个 fd_set
变量以后可以使用如下所示几个宏进行操作,下列操作用来设置、清除、判断文件描述符集合:
操作 | 函数原型 | 描述 |
---|---|---|
清除文件描述符集合 | FD_ZERO(fd_set *set) | 清除一个文件描述符集合 |
添加文件描述符 | FD_SET(int fd, fd_set *set) | 将一个文件描述符加入文件描述符集合中 |
清除文件描述符 | FD_CLR(int fd, fd_set *set) | 将一个文件描述符从文件描述符集合中清除 |
判断文件描述符 | FD_ISSET(int fd, fd_set *set) | 判断文件描述符是否被置位 |
使用 select 函数对某个设备驱动文件进行读非阻塞访问的操作示例如下所示:
void main(void)
{int ret, fd;fd_set readfds;struct timeval timeout;fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */FD_ZERO(&readfds); /* 清除 readfds */FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 里面 *//* 构造超时时间 */timeout.tv_sec = 0;timeout.tv_usec = 500000; /* 500ms */ret = select(fd + 1, &readfds, NULL, NULL, &timeout);switch (ret) {case 0: /* 超时 */printf("timeout!\r\n");break;case -1: /* 错误 */printf("error!\r\n");break;default: /* 可以读取数据 */if(FD_ISSET(fd, &readfds)) { /* 判断是否为 fd 文件描述符 *//* 使用 read 函数读取数据 */../* 使用 read 函数读取数据 */}break;}
}
引出下面POLL,在单个线程中, select 函数能够监视的文件描述符数量有最大的限制,一般为
1024
,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!#undef __FD_SETSIZE #define __FD_SETSIZE 1024typedef struct {unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))]; } __kernel_fd_set;
2.3.2 POLL函数
在单个线程中, select 函数能够监视的文件描述符数量有最大的限制,一般为 1024
,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用 poll 函数, poll 函数本质上和 select 没有太大的差别,但是 poll 函数没有最大文件描述符限制, Linux 应用程序中 poll 函数原型如下所示:
poll() 的功能和实现原理与 select() 相似,其函数原型为:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds
: 要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体 pollfd类型的,pollfd
结构体如下所示:
struct pollfd {int fd; /* 文件描述符 */short events; /* 请求的事件 */short revents; /* 返回的事件 */
};
- fd 是要监视的文件描述符,如果 fd 无效的话那么 events 监视事件也就无效,并且 revents返回 0。 events 是要监视的事件,可监视的事件类型如下所示:
事件类型 | 描述 |
---|---|
POLLIN | 有数据可以读取。 |
POLLPRI | 有紧急的数据需要读取。 |
POLLOUT | 可以写数据。 |
POLLERR | 指定的文件描述符发生错误。 |
POLLHUP | 指定的文件描述符挂起。 |
POLLNVAL | 无效的请求。 |
POLLRDNORM | 等同于 POLLIN。 |
-
revents 是返回参数,也就是返回的事件, 由 Linux 内核设置具体的返回事件。
-
nfds: poll 函数要监视的文件描述符数量。
-
timeout: 超时时间,单位为 ms。
-
返回值:返回 revents 域中不为 0 的 pollfd 结构体个数,也就是发生事件或错误的文件描述符数量; 0,超时; -1,发生错误,并且设置 errno 为错误类型。
使用 poll 函数对某个设备驱动文件进行读非阻塞访问的操作示例如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>void main(void)
{int ret;int fd1, fd2;struct pollfd fds[2]; // 用于存储两个设备文件的轮询信息// 打开两个设备文件,设置为非阻塞模式fd1 = open("device1", O_RDWR | O_NONBLOCK);if (fd1 < 0) {perror("open device1 failed");exit(EXIT_FAILURE);}fd2 = open("device2", O_RDWR | O_NONBLOCK);if (fd2 < 0) {perror("open device2 failed");close(fd1); // 关闭已打开的文件描述符exit(EXIT_FAILURE);}// 构造轮询结构体fds[0].fd = fd1;fds[0].events = POLLIN; // 监视设备1是否可读fds[1].fd = fd2;fds[1].events = POLLIN; // 监视设备2是否可读// 轮询两个设备文件,超时时间设置为1000msret = poll(fds, 2, 1000);if (ret > 0) {// 检查设备1是否可读if (fds[0].revents & POLLIN) {printf("Device1 is readable\n");// 在这里可以对设备1进行读操作// 示例代码省略读操作部分}// 检查设备2是否可读if (fds[1].revents & POLLIN) {printf("Device2 is readable\n");// 在这里可以对设备2进行读操作// 示例代码省略读操作部分}} else if (ret == 0) {printf("Poll timeout\n");} else {perror("poll failed");}// 关闭文件描述符close(fd1);close(fd2);
}
2.3.3 epoll函数
当多路复用的文件数量庞大、I/O 流量频繁的时候,一般不太适合使用 select() 和 poll(),此种情况下,select() 和 poll() 的性能表现较差,我们宜使用 epoll
。epoll
的最大好处是会随着 fd
的数目增长而降低效率,select() 则会随着 fd
的数量增大性能下降明显。
与 epoll
相关的用户空间编程接口包括:
int epoll_create(int size);
epoll 就是为处理大并发而准备的,一般常常在网络编程中使用 epoll 函数。
2.3.4 Linux 驱动下的 poll 操作函数
当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations 操作集中的 poll 函数就会执行。所以驱动程序的编写者需要提供对应的 poll 函数, poll 函数原型如下所示:
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
- filp: 要打开的设备文件(文件描述符)。
- wait: 结构体
poll_table_struct
类型指针,轮询表指针, 由应用程序传递进来的。一般将此参数传递给poll_wait
函数。 - 返回值:返回设备资源的可获取状态,即 POLLIN、POLLOUT、POLLPRI、POLLERR、POLLNVAL 等宏的位“或”结果。每个宏的含义都表明设备的一种状态,如POLLIN(定义为 0x0001)意味着设备可以无阻塞地读,POLLOUT(定义为 0x0004)意味着设
备可以无阻塞地写。可以返回的资源状态如下:
事件类型 | 描述 |
---|---|
POLLIN | 有数据可以读取。 |
POLLPRI | 有紧急的数据需要读取。 |
POLLOUT | 可以写数据。 |
POLLERR | 指定的文件描述符发生错误。 |
POLLHUP | 指定的文件描述符挂起。 |
POLLNVAL | 无效的请求。 |
POLLRDNORM | 等同于 POLLIN,普通数据可读 |
这个函数应该进行两项工作:
1)对可能引起设备文件状态变化的等待队列调用 poll_wait() 函数,将对应的等待队列头部添加到 poll_table 中。
2)返回表示是否能对设备进行无阻塞读、写访问的掩码。
这里一定注意我们的视角从前面的应用端APP,转换到了一个具体的设备驱动。
用于向 poll_table 注册等待队列的关键 poll_wait()
函数的原型如下:
void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table * wait);
poll_wait() 函数的名称非常容易让人产生误会,以为它和 wait_event() 等一样,会阻塞地等待某事件的发生,其实这个函数并不会引起阻塞。poll_wait 函数不会引起阻塞, poll_wait() 函数所做的工作是把当前进程--应用程序
添加到 wait 参数指定的等待列表(poll_table)中,实际作用是让唤醒参数 queue 对应的等待队列可以唤醒因 select() 而睡眠的进程。
struct poll_table_struct;
/* * structures and helpers for f_op->poll implementations*/
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);/** Do not touch the structure directly, use the access functions* poll_does_not_wait() and poll_requested_events() instead.*/
typedef struct poll_table_struct {poll_queue_proc _qproc;unsigned long _key;
} poll_table;
//poll_table 结构用于管理 poll 系统调用的等待队列。
这里详细看一下这里的逻辑:
poll_table
是一个用于管理多个设备等待队列的结构。它的主要作用是将多个设备的等待队列头(wait_queue_head_t
)集中管理起来,以便在调用poll
函数时能够高效地处理多个设备的等待队列。关于 poll_table 的管理机制
- 当应用程序调用
poll
或select
系统调用时,内核会调用设备驱动程序中定义的poll
函数。unsigned int (*poll)(struct file *filp, struct poll_table_struct *wait);
其中,wait
是一个指向poll_table_struct
的指针,它是一个内核管理的结构,用于保存当前设备的等待队列头。- 在
poll
函数中,驱动程序需要使用poll_wait
宏来将当前设备的等待队列头(wait_queue_head_t
)添加到poll_table
中。poll_table
是动态管理的。当一个设备的等待队列头被添加到poll_table
后,内核会负责维护这些等待队列头。如果设备有数据可读或可写,内核会唤醒对应的等待队列中的任务。
poll_table
是由内核管理的,它用于集中管理多个设备的等待队列头。
- 每个设备驱动程序通常会定义自己的等待队列头(
wait_queue_head_t
)。例如,在一个 LED 驱动中,可能会定义一个等待队列头,用于等待 LED 状态变化的通知。而在一个按键驱动中,也会定义一个等待队列头,用于等待按键事件。 - 当应用程序调用
poll
函数时,内核会创建一个poll_table
,并将所有相关设备的等待队列头添加到这个poll_table
中。这样,内核可以统一管理这些等待队列头,而不需要每个驱动程序单独管理。
通过以上分析,可得出设备驱动中poll函数的典型模板:
static unsigned int xxx_poll(struct file *filp, poll_table *wait)
{unsigned int mask = 0;struct xxx_dev *dev = filp->private_data; /* 获得设备结构体指针 */...poll_wait(filp, &dev->r_wait, wait); /* 加入读等待队列 */poll_wait(filp, &dev->w_wait, wait); /* 加入写等待队列 */if (data_available)mask |= POLLIN | POLLRDNORM; /* 可读 *//* 标示数据可获得(对用户可读)*/if (...)mask |= POLLOUT | POLLWRNORM; /* 可写 *//* 标示数据可写入 */...return mask;
}
2.4 实验示例
相关文章:
Linux驱动开发--阻塞、非阻塞I/O
2. 阻塞、非阻塞I/O IO 指的是 Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到…...
运筹学之模拟退火
目录 一、历史二、精髓思想三、案例与代码实现 一、历史 问:谁在什么时候提出模拟退火?答:模拟退火算法(Simulated Annealing,SA)是由斯图尔特柯尔斯基(Scott Kirkpatrick) 等人在 …...
JavaScript 的演变:2023-2025 年的新特性解析
随着Web技术的飞速发展,ECMAScript(简称ES)作为JavaScript的语言标准,也在不断进化。 本文将带你学习 ECMAScript 2023-2025 的新特性。 一、ECMAScript 2023 新特性 1.1 数组的扩展 Array.prototype.findLast()/Array.protot…...
CSS继承
CSS继承 CSS继承是一种机制,允许子元素自动继承父元素的某些样式属性,从而减少重复代码。 以下是一些常见的具有继承性的CSS属性: color : 文字颜色 font-family : 字体族名称 font-size : 字体大小 font-weight &am…...
游戏引擎学习第236天:GPU 概念概述
回顾并展望通过视频采集卡进行流媒体传输的未来 昨天,我们迈出了大胆的一步,决定初始化硬件的 3D 加速,因为我有点厌倦了我们的游戏没有垂直同步(vsync)。如今,在 Windows 上,我找不到一种可靠…...
HFSS3(limy)——建模学习记录
前言——笔者使用的是21版HFSS 1.基本模型 为什么没有环形的天线 2.创建基本模型方法 常用:先粗略建好模型再编辑输入准确坐标和大小尺寸(这里长方体起始点是左上角下方的点,也就是说要输入模型起点相对于坐标原点的位置尺寸就可以确定具体…...
php实现zip压缩
可以使用ZipArchive类来创建ZIP压缩文件。ZipArchive是PHP内置的一个类,提供了创建、打开、读取、写入和关闭ZIP文件的功能。 示例:压缩单个文件 <?php$fileToZip path/to/your/file.txt; $zipFileName compressed.zip;$zip new ZipArchive(); …...
【STM32单片机】#10 USART串口通信
主要参考学习资料: B站江协科技 STM32入门教程-2023版 细致讲解 中文字幕 开发资料下载链接:https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwddspb 单片机套装:STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协 实验&…...
goc命令大全
颜色0黑1红2蓝3浅绿4浅蓝5淡黄6棕7深蓝8灰9粉10深绿11紫12蓝绿13黄14橙15白 绘图命令功能pen笔.fd()前进.rt()右转.c()颜色.up()抬笔.o(,)圆.e(,)椭圆.r(,,)长方形.picL(,)调图片.text(,,)文字.hide()隐藏.moveTo(,)移动wait();等待.soundL()调声音pause();暂停 绘图命令功能…...
Linux | 软件仓库管理
一. 软件包 1.1 软件包的分类 DEB:主要用于基于 Debian 的系统,如 Ubuntu。这种软件包格式具有良好的依赖管理机制,方便用户安装、升级和卸载软件。RPM:广泛应用于 Red Hat、CentOS、Fedora 等系统。RPM 包将软件打包成一个文件…...
Python实现对目标Word文档进行自动化排版【4万字精讲】(14)
前言 本文是该专栏的第14篇,后面会持续分享Python办公自动化干货知识,记得关注。 注意:本文涵盖4万字以及实战操作代码的精讲攻略,带你轻松掌握一键式“文档自动化排版”程序功能。 如果说当你在工作项目中,遇到这样的需求,需要如何处理——假设,现在有大批量的docx格…...
LeetCode每日一题4.19
2563. 统计公平数对的数目 题目 问题分析 输入:一个整数数组 nums 和两个整数 lower 和 upper。 输出:返回满足条件的公平数对的数目,即对于所有 0 < i < j < n,lower < nums[i] nums[j] < upper 的数对 (i, j)…...
Spring AI 开发 - 快速入门
先看效果 项目搭建 Spring AI 是 Spring 推出的一个项目,目标是提供统一的API抽象层,屏蔽不同AI模型和服务的底层差异,实现跨平台兼容性。 演示使用的模型是阿里的 qwq-32b。 环境要求: JDK :17以上(包括…...
leetcode哈希表(六)-三数相加
题目 15. 三数之和 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复…...
Photoshop安装与配置--简单攻略版
下载地址:Photoshop软件工具下载 安装完成后,即可运行Photoshop.exe;打开工具页面后,按照下面简单配置即可 1.编辑-》首选项-》常规 或者直接快捷键CtrlK 暂存盘:一定要设置为非C盘 2.性能 3.文件处理 以上配置比较基础…...
一个 CTO 的深度思考
今天和一些同事聊了一会,以下是我的观点 我的观点,成年人只能筛选,不能培养在组织中,应该永远向有结果的人看齐。不能当他站出来讲话的时候,大家还要讨论讨论,他虽然拿到结果了,但是他就是有一…...
Java:使用Maven构建项目无src解决方案
创建箭头所指的包和类以及yml文件: 类中: package com.itheima;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication public class BigEventApplication …...
Git 命令速查手册
听说用美图可以钓读者? 一、基础操作核心命令 1. 仓库初始化与克隆 命令作用示例git init创建新仓库git init my-projectgit clone克隆远程仓库git clone [https://github.com/user/repo.git](https://github.com/user/repo.git)git remote add关联远程仓库git re…...
office软件中word里面的编号库和列表库功能
在Microsoft Word中,编号库和列表库是两大核心排版工具,分别服务于不同层级的文档结构化需求。以下从功能定义、核心差异、应用场景及操作技巧等维度进行详细解析: 一、编号库(Numbering Library) 1. 定义与功能 编号库是Word中预设或用户自定义的有序列表格式集合,用于…...
C++入门七式——模板初阶
目录 函数模板 函数模板概念 函数模板格式 函数模板的原理 函数模板的实例化 模板参数的匹配原则 类模板 类模板的定义格式 类模板的显式实例化 当面对下面的代码时,大家会不会有一种无力的感觉?明明这些代码差不多,只是因为类型不…...
Python+Selenium+Pytest+POM自动化测试框架封装(完整版)
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 1、测试框架简介 1)测试框架的优点 代码复用率高,如果不使用框架的话,代码会显得很冗余。可以组装日志、报告、邮件等一…...
matlab 处理海洋数据并画图的工具包--ocean_data_tools
matlab 处理海洋数据并画图的工具包–ocean_data_tools matlab 处理海洋数据并画图的工具包–ocean_data_tools ocean_data_tools 简化了提取、格式化和可视化免费可用的海洋学数据的过程。虽然可以在线访问大量海洋学数据,但由于获取这些数据并将其格式化为可用数据…...
软件开发指南——GUI 开发方案推荐
1. LVGL (Light and Versatile Graphics Library) 适用场景:嵌入式设备、资源受限环境 优势: 专为嵌入式设计的开源 GUI 库,内存占用极小(最低仅需 64KB RAM)支持触摸屏、硬件加速(如 STM32 的 LTDC&…...
JDK8 HashMap的实现原理
一 HashMap底层存储结构 HashMap底层结构采用(数组)(链表 or 红黑树)的形式来存储节点。 首先HashMap是一个数组,而且数组里面每个位置可以放入多个元素,形象一点,咱们把数组的这些个位置称为桶…...
YOLO拓展-锚框(anchor box)详解
一.锚框(anchor box)概述 1.1什么是锚框 锚框就是一种进行预测的像素框,通过遍历输入图像上所有可能的像素框,然后选出正确的目标框,并对位置和大小进行调整就可以完成目标检测任务。 对于yolo锚框的建设须基于实际…...
U-Boot(Universal Bootloader)简介
U-Boot 是一种开源的、高度可定制的 引导加载程序(Bootloader),专为嵌入式系统和特定硬件平台设计。它负责在设备上电后初始化硬件、加载操作系统内核,并将控制权移交给操作系统,是嵌入式设备启动过程中不可或缺的核心…...
turtle库绘制进阶图形
要求: 1.绘制嵌套彩色五角星(大小逐层递减) 2. 设计函数绘制自定义正多边形(边数与颜色参数化) 3. 扩展:实现动态旋转花瓣图案 代码: import turtledef draw_nested_star():colors ["…...
[matlab]南海地形眩晕图代码
[matlab]南海地形眩晕图代码 请ChatGPT帮写个南海地形眩晕图代码 图片 图片 代码 .rtcContent { padding: 30px; } .lineNode {font-size: 12pt; font-family: "Times New Roman", Menlo, Monaco, Consolas, "Courier New", monospace; font-style: n…...
3. 进程概念
目录 1. 冯诺依曼体系结构 2. 操作系统 3. 理解进程的一般思路 4. 查看进程 5. fork初识 6. 进程状态 6.1 一般操作系统 6.2 Linux系统是怎么维护进程状态的 7. 进程优先级 先谈硬件-再谈软件-最后谈进程。 1. 冯诺依曼体系结构 我们常见的计算机(笔记本电…...
yolov8的数据处理lableimg的安装以及使用
视频数据集准备 video cv2.VideoCapture("./BVN.mp4") num 0 # 计数器 save_step 30 # 间隔帧 while True:rel, frame video.read()if not ret:breaknum 1if num % save_step 0:cv2.imwrite("./demo images/" str(num) ".jpg", frame)l…...
小刚说C语言刷题——1035 判断成绩等级
1.题目描述 输入某学生成绩,如果 86分以上(包括 86分)则输出 VERY GOOD ,如果在 60到 85之间的则输出 GOOD (包括 60和 85),小于 60 的则输出 BAD。 输入 输入只有一行,包括 1个整数。 输出 输出只有一行…...
Spring 依赖冲突解决方案详解
引言 在Spring框架中,依赖管理是一个核心功能,它使得开发者能够轻松地管理应用程序中的各种组件和服务。然而,随着项目的增长和复杂度的增加,依赖冲突问题也变得日益常见。本文将详细介绍Spring中不同类型的依赖冲突及其解决方法…...
P11299 [NOISG 2021 Finals] Fraud 题解
题目背景 你被任命为第 24 届全国信息学奥林匹克竞赛的负责人! 题目描述 本次竞赛共有 N 名参赛者和 2 轮比赛。第 i 名参赛者在第一轮获得了分,在第二轮获得了 分。 每轮比赛分别有一个正整数权重 X 和 Y。第 i 名参赛者的最终得分 计算公式为&a…...
AI时代下 你需要和想要了解的英文缩写含义
在AI智能时代下,越来愈多的企业都开始重视并应用以及开发AI相关产品,这个时候都会或多或少的涉及到英文,英文还好,但是如果是缩写,如果我们没有提前了解过,我们往往很难以快速Get到对方的意思。在这里&…...
大数据平台简介
一、分布式系统基础架构 (一)定义与核心特征 分布式系统是由多台计算机(节点)通过网络协作组成的系统,对外表现为一个统一整体。其核心特征包括: 去中心化:节点平等或分角色协作(如…...
电脑端移植至手机平板:攻克难题,仙盟架构显神通——仙盟创梦IDE
在将电脑端应用移植到手机和平板的过程中,常面临诸多棘手问题。像 1.x 号关闭按钮因位置设计欠佳,难以被用户精准点击,字体过小导致阅读与操作不便等。未来之窗仙盟创梦凭借创新的仙盟架构,巧妙且高效地化解了这些困扰开发者与用户…...
基于Python的中国象棋小游戏的设计与实现
基于Python的中国象棋小游戏的设计与实现 第一章 绪论1.1 研究背景1.2 研究意义 第二章 需求分析2.1 需求分析2.1.1核心功能需求2.1.2 用户体验需求2.1.3 衍生功能需求 2.2 可行性分析2.2.1 技术可行性2.2.2 经济可行性2.2.3 市场可行性2.2.4 法律与合规性 第三章 概要设计3.1 …...
HCIP --- OSPF综合实验
一、拓扑图 二、实验要求 1,R5为ISP,其上只能配置IP地址;R4作为企业边界路由器,出口公网地址需要通过PPP协议获取,并进行chap认证。 2,整个0SPF环境IP基于172.16.0.8/16划分。 3,所有设备均可访问R5的环…...
【OpenGL】OpenGL学习笔记-1:VS2019配置OpenGL开发环境
在Visual Studio 2019中可以通过手动配置库文件或NuGet包管理器快速安装的方法配置OpenGL环境,详细步骤如下: 一、打开VS2019,创建新的控制台项目 二、方法一:手动配置GLEW/GLFW/GLAD库 GLFW是窗口管理和输入事件的基础设施&…...
GWAS_LD
局部LDblock 绘图 1. 查看显著位点附近基因情况 链接pvalue显著位点文件 ln -s ~/yiyaoran/GWAS/my_GWAS_J/P3.GWAS/01.tassel/mlm_output.manht_figure.sigSite.out . #也可以自己筛选awk $2 9 && $4 < 0.000028481 mlm_output.manht_input>368_GWAS.snpsnp两…...
WinForms开发基础:实现带X按钮的ClearableTextBox控件
前言 我们经常看到这样的带X按钮的输入框 如果使用WinForms开发中,该如何进行设计,普通的TextBox控件如何进行改造?为了提升用户体验,在TextBox文本框内添加一个“x”按钮,方便用户一键清除内容。本文将介绍如何通过继…...
直线轴承常规分类知多少?
直线轴承的分类方式多样,以下是从材质、结构形状和常规系列三个维度进行的具体分类: 按主要材质分类 外壳材质:常见的有不锈钢,具有良好的耐腐蚀性,适用于一些对环境要求较高、易受腐蚀的工作场景;轴承…...
算法期末复习
算法期末复习 1.单选题 \1. 二分搜索算法是利用( A)实现的算法。 A. 分治策略 B. 动态规划法 C. 贪心法 D. 回溯法 \2. 回溯法解旅行售货员问题时的解空间树时( C ) 。 A. 子集树 B. 深度优先生成树 C. 排序树 D. 广度优先生成树 \3. 下列算法中通常以自底向上的方式求解最…...
LeetCode 5:最长回文子串
1、题目描述 给你一个字符串 s,找到 s 中最长的 回文 子串。 示例 1: 输入:s "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。 示例 2: 输入:s "cbbd" 输出&#…...
2025年4月19日 记录大模型出现的计算问题
2025年4月19日 记录大模型出现的计算问题,用了四个大模型计算json的数值,3个错误,1个正确 问题 Class Train Val answer 2574 853 screen 5025 1959 blackBoard 7847 3445 teacher 8490 3228 stand…...
Python语法系列博客 · 第3期 数据结构入门(列表、元组、字典、集合)
上一期小练习解答(第2期回顾) ✅ 练习1:判断一个数是正数、负数还是零 num float(input("请输入一个数:")) if num > 0:print("正数") elif num < 0:print("负数") else:print("零&q…...
【对Linux文件权限的深入理解】
Linux文件权限 Linux下权限概念概念相关命令 Linux的文件权限管理1.文件访问者的分类(⼈)文件类型和访问权限(事物属性)文件权限值的表示方法⽂件访问权限的相关设置方法目录的权限(比较重要)粘滞位 Linux下…...
2025.04.19【Spider】| 蜘蛛图绘制技巧精解
Basic multi-group radar chart Start with a basic version, learn how to format your input dataset Radar chart with ggradar A Spider chart made using the ggradar package and a lot of customization.A work by Tuo Wang 文章目录 Basic multi-group radar chartRa…...
AtCoder ABC402 A~D 题解
A - CBC 题目大意 给点字符串 S S S,输出其中所有大写字母。 思路 根据题意模拟即可。 代码 #include <cstdio> #include <iostream> #include <algorithm> using namespace std;int main() {string s;cin >> s;for (int i 0; i &l…...
双指针算法(部分例题解析)
快慢指针左右指针 前言 双指针,它通过设置两个指针来遍历数据,从而实现高效的查找、排序、去重等操作。双指针算法的核心在于通过合理地移动这两个指针,减少不必要的遍历,提高算法的效率。 283. 移动零 - 力扣(LeetCo…...