eventfd 和 epoll 的结合使用

一.eventfd介绍
eventfd 是 Linux 的一个系统调用,创建一个文件描述符用于事件通知,自 Linux 2.6.22 以后开始支持。

接口及参数介绍

#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);

eventfd() 创建一个 eventfd 对象,可以由用户空间应用程序实现事件等待/通知机制,或由内核通知用户空间应用程序事件。
该对象包含了由内核维护的无符号64位整数计数器 count 。使用参数 initval 初始化此计数器。

struct eventfd_ctx {struct kref kref;wait_queue_head_t wqh;/** Every time that a write(2) is performed on an eventfd, the* value of the __u64 being written is added to "count" and a* wakeup is performed on "wqh". A read(2) will return the "count"* value to userspace, and will reset "count" to zero. The kernel* side eventfd_signal() also, adds to the "count" counter and* issue a wakeup.*/__u64 count;unsigned int flags;
};

flags 可以是以下值的 OR 运算结果,用以改变 eventfd 的行为。

  • EFD_CLOEXEC (since Linux 2.6.27)
    文件被设置成 O_CLOEXEC,创建子进程 (fork) 时不继承父进程的文件描述符。
  • EFD_NONBLOCK (since Linux 2.6.27)
    文件被设置成 O_NONBLOCK,执行 read / write 操作时,不会阻塞。
  • EFD_SEMAPHORE (since Linux 2.6.30)
    提供类似信号量语义的 read 操作,简单说就是计数值 count 递减 1。

操作方法
一切皆为文件是 Linux 内核设计的一种高度抽象,eventfd 的实现也不例外,我们可以使用操作文件的方法操作 eventfd。

read(): 读取 count 值后置 0。如果设置 EFD_SEMAPHORE,读到的值为 1,同时 count 值递减 1。read
write(): 其实是执行 add 操作,累加 count 值。
epoll()/poll()/select(): 支持 IO 多路复用操作。
close(): 关闭文件描述符,eventfd 对象引用计数减 1,若减为 0,则释放 eventfd 对象资源。

那获取到eventfd 文件描述符后,我们对其可以做哪些操作呢?

static const struct file_operations eventfd_fops = {
#ifdef CONFIG_PROC_FS.show_fdinfo    = eventfd_show_fdinfo,
#endif.release        = eventfd_release,.poll           = eventfd_poll,.read           = eventfd_read,.write          = eventfd_write,.llseek         = noop_llseek,
};

通过上面 eventfd 实现的调用可知, 我们可以对eventfd进行 read、write、poll、close等操作。
接下来我们通过一个例子来了解下 eventfd 的具体使用,完整代码可用过 man eventfd 获取 。

int main(int argc, char *argv[])
{int efd;uint64_t u;ssize_t s;//创建一个eventfd对象,返回一个文件描述符efd = eventfd(0, 0);switch (fork()) {case 0: //子进程for (int j = 1; j < argc; j++) {printf("Child writing %s to efd\n", argv[j]);u = strtoull(argv[j], NULL, 0);//向eventfd内部写一个8字节大小的数据s = write(efd, &u, sizeof(uint64_t));}printf("Child completed write loop\n");exit(EXIT_SUCCESS);default: //父进程sleep(2); //先休眠2秒,等待子进程写完数据printf("Parent about to read\n");//从eventfd中读取数据s = read(efd, &u, sizeof(uint64_t));rintf("Parent read %"PRIu64" (%#"PRIx64") from efd\n", u, u);exit(EXIT_SUCCESS);case -1:handle_error("fork");}
}

执行结果:

$ ./a.out 1 2 4 7 14Child writing 1 to efdChild writing 2 to efdChild writing 4 to efdChild writing 7 to efdChild writing 14 to efdChild completed write loopParent about to readParent read 28 (0x1c) from efd  // 父进程读到的值为28(1+2+4+7+14)

由于 eventfd 实现的逻辑是累计计数,因此上述例子中父进程读取到的是总计数,读完后内核中的计数会清零。
当用户调用 write 时,内核中会调用 eventfd_write 接口;
eventfd_write 实现了如下功能:

获取用户要写的数据 ucnt,判断 ucnt 和当前 ctx->count 的和是否在最大值范围内:
若在范围内,则把 ucnt 加入到 ctx->count 中,返回。
若超过范围,则判断是否需要阻塞:
若是阻塞调用,则进程休眠,直到满足二者之和在最大值范围内。
若非阻塞调用,则直接返回。

当用户调用 read 时,内核中会调用 eventfd_read 接口。
eventfd_read 实现了如下功能:

判断是否可读(ctx->count),若可读,则读取数据,把数据拷贝到用户态,返回。
若不可读,判断是否是阻塞读:
若是阻塞读,进程休眠,直到可读。
若是非阻塞读,则直接返回。
另外,读取数据时,若是以信号量的方式读取,则每次读到的值为1,计数器减1,否则读取计数器全部的值,同时计数器清零。

二、epoll函数介绍
与 poll 不同的是,epoll 本身并不是一个系统调用。它是一个允许进程在多个文件描述符上复用 I/O 的内核数据结构。
该数据结构通过以下三个系统调用创建、修改、删除。

epoll_create
size参数向内核指定内核进程需要监控的文件描述符的个数,这有助于内核决定epoll实例的大小。从Linux2.6.8开始,这个参数就被忽略了,因为epoll数据结构会随着文件描述符的添加或删除而动态调整大小。

进程通过调用epoll_create来创建epoll实例,后续通过epoll返回的指向epoll实例的文件描述符来进行各种操作,比如添加、删除或者修改它想要件事epoll实例的I/O的其他文件描述符。

#include <sys/epoll.h>
int epoll_create(int size);

在Linux系统中,还有另外一个系统调用函数epoll_create1,其声明如下:

int epoll_create1(int flags);

其中,flags参数可以是0或EPOLL_CLOEXEC。
当flags为0时候,epoll_create1(0)与epoll_create功能一致。
如果设置为EPOLL_CLOEXEC,那么由当前进程fork出来的任何子进程,其都会关闭其父进程的epoll实例所指向的文件描述符,也就是说子进程没有访问父进程epoll实例的权限。
需要注意的是,与epoll实例关联的文件描述符需要通过close()系统调用来释放。多个进程可能持有同一epoll实例的描述符。这是因为,假如没有设置EPOLL_CLOEXEC标志的fork将把描述符复制到子进程中的epoll实例,当这些进程中的某一个或者多个进程关闭了其中一个文件描述符,那么可能会导致程序的不可用,或者不在我们的预期之内。

 有一点需要特别注意,关联 epoll 实例的文件描述符需要通过close()系统调用来释放。多个进程可能持有同一个 epoll 实例的文件描述符(如:当 EPOLL_CLOEXEC标记没有指定时,fork 出来的子进程会复制该文件描述符)。当所有的进程都不再使用该描述符时(通过调用 close() 或者退出),内核才会销毁 epoll 实例。

epoll_ctl
进程可以通过 epoll_ctl 来添加它想要监听的描述符给 epoll 实例。所有注册到 epoll 实例的文件描述符统称为epoll set 或interest list。
在这里插入图片描述上图中,pid 为 483 的进程在 epoll 实例中注册了 FD1,FD2,FD3,FD4,FD5 文件描述符。以上就是 epoll 实例的 interest list 或者 epoll set。随后,当任何文件描述符已经准备好 I/O 时,它们就会放到 ready list 中。ready list 是 interest list 的子集,如图所示:
在这里插入图片描述
epoll_ctl函数的声明如下:

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

在这里插入图片描述

  • epfd: epoll_create函数返回的文件描述符,用于标识内核中的epoll实例。
  • fd:需要被操作的文件描述符
  • op:对fd文件描述符的操作类型。主要有如下几个
  • EPOLL_CTL_ADD 向epfd实例进行注册,在有I/O事件时候获得通知
  • EPOLL_CTL_DEL 从EPOL实例中删除/注销fd。这意味着进程将不再收到关于该文件描述符上事件的任何通知 (EPOLL_CTL_DEL )。如果文件描述符已添加到多个EPOL实例中,则关闭它将从添加到该实例的所有EPOL目标监控列表中删除它。
  • EPOLL_CTL_MOD 修改正在监视的fd事件
  • 在这里插入图片描述
    event: 一个指向一个名为epoll_event的结构的指针,它存储了我们实际要监视fd的事件

在这里插入图片描述
以下是 epoll_event 结构体:

struct epoll_event
{uint32_t events;   /* Epoll events */epoll_data_t data;    /* User data variable */
} __attribute__ ((__packed__));typedef union epoll_data
{void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;

epoll_event事件结构的第一个字段事件是一个位掩码,它指示要监视哪个事件fd。
在这里插入图片描述
epoll_event事件结构的第二个字段是一个联合字段。

epoll_wait
epoll_wait系统调用,用来监视epoll set/interest集上发生的事件。如果被监视的epoll set/interest集上没有任何I/O事件,则该调用会一直被阻塞,直至有I/O事件产生。
该函数声明如下:

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
  • epfd: epoll_create函数返回的文件描述符,用于标识内核中的epoll实例。
  • evlist: epoll事件结构的数组。evlist由调用进程分配,当epoll_wait返回时,修改此数组以指示有关目标监控列表中处于就绪状态的文件描述符子集的信息(这称为就绪列表)
  • maxevents : evlist数组大小
  • timeout:此参数的意思与poll或select相同。此值指定epoll_wait系统调用的阻塞时间
    当设置为0时,代表该函数不会被阻塞,其在检查完目标监控列表中有无I/O事件之后,马上就返回。

当设置为-1时候,该函数将被永久阻塞,进程将处于休眠状态,直到满足下面两个条件(1) 有I/O事件发生 (2) 被信号处理程序中断。
当设置为非负值和非零值时,epoll_wait将阻塞,直到满足有如下几个条件之一(1) 在epfd的目标监控列表中指定的一个或多个描述符就绪,(2) 调用被信号处理程序中断 (3) timeout毫秒指定的时间量已过期。

epoll_wait 函数的返回值有以下几种:
如果发生错误(EBADF或EINTR或EFAULT或EINVAL),则返回代码为-1
如果调用在目标监控列表中的任何文件描述符就绪之前超时,则返回代码为0。
如果目标监控列表中的一个或多个文件描述符准备就绪,则返回代码为正整数,表示evlist数组中的文件描述符总数。然后检查evlist以确定哪些事件发生在哪些文件描述符上。

用下面的代码进行模拟:

#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <pthread.h>
#include <sys/eventfd.h>
#include <sys/epoll.h>int event_fd = -1;void *read_thread(void *dummy)
{uint64_t inc = 1;int ret = 0;int i = 0;for (; i < 2; i++) {ret = write(event_fd, &inc, sizeof(uint64_t));if (ret < 0) {perror("child thread write event_fd fail.");} else {printf("child thread completed write %llu (0x%llx) to event_fd\n", (unsigned long long) inc, (unsigned long long) inc);}sleep(4);}
}int main(int argc, char *argv[])
{int ret = 0;pthread_t pid = 0;event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);if (event_fd < 0) {perror("event_fd create fail.");}ret = pthread_create(&pid, NULL, read_thread, NULL);if (ret < 0) {perror("pthread create fail.");}uint64_t counter;int epoll_fd = -1;struct epoll_event events[16];if (event_fd < 0){printf("event_fd not inited.\n");}epoll_fd = epoll_create(8);if (epoll_fd < 0){perror("epoll_create fail:");}struct epoll_event read_event;read_event.events = EPOLLIN;read_event.data.fd = event_fd;ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, event_fd, &read_event);if (ret < 0) {perror("epoll_ctl failed:");}while (1) {printf("main thread epoll is waiting......\n");ret = epoll_wait(epoll_fd, events, 16, 2000);printf("main thread epoll_wait return ret : %d\n", ret);if (ret > 0) {int i = 0;for (; i < ret; i++) {int fd = events[i].data.fd;if (fd == event_fd) {uint32_t epollEvents = events[i].events;if (epollEvents & EPOLLIN) {ret = read(event_fd, &counter, sizeof(uint64_t));if (ret < 0) {printf("main thread read fail\n");} else {printf("main thread read %llu (0x%llx) from event_fd\n", (unsigned long long) counter, (unsigned long long) counter);}} else {printf("main thread unexpected epoll events on event_fd\n");}}}} else if (ret == 0) {printf("main thread epoll_wait timed out. continue epoll\n");} else {perror("main thread epoll_wait error.");}}
}

设置 eventfd 的计数器初始值为 0 且 flags 为 EFD_NONBLOCK | EFD_CLOEXEC。执行实例代码,结果如下(为了方便分析,让每一次写完阻塞 4 秒,epoll_wait 的超时时间为 2 秒):

wufan@Frank-Linux:~/Linux/test$ ./epoll_eventfd 
main thread epoll is waiting...... // main 线程阻塞在读端
child thread completed write 1 (0x1) to event_fd // 第一次写入后阻塞 4 秒
main thread epoll_wait return ret : 1 // 第一次写完后,立即唤醒 main 线程去进行读操作
main thread read 1 (0x1) from event_fd // main 线程读到了数据
main thread epoll is waiting...... // main 线程阻塞又在读端,超时时间为 2 秒
main thread epoll_wait return ret : 0 // main 线程阻塞等待时间到,返回
main thread epoll_wait timed out. continue epoll
main thread epoll is waiting...... // main 线程阻塞又在读端,超时时间为 2 秒
child thread completed write 1 (0x1) to event_fd // // 第二次写入后阻塞 4 秒
main thread epoll_wait return ret : 1 // 第二次写完后,立即唤醒 main 线程去进行读操作
main thread read 1 (0x1) from event_fd // main 线程读到了数据
main thread epoll is waiting...... // main 线程阻塞又在读端,超时时间为 2 秒
main thread epoll_wait return ret : 0 // main 线程阻塞等待时间到,返回
main thread epoll_wait timed out. continue epoll
main thread epoll is waiting...... // main 线程阻塞又在读端,超时时间为 2 秒
只要没有写入数据,就会在这个死循环中阻塞 -> 超时 -> 阻塞...

在通过实例来理解 eventfd 函数机制中,我们知道了 eventfd 的 EFD_NONBLOCK 模式下,读到计数器的值为 0 后,再继续读,会直接返回一个错误值,不会阻塞。但是上述的例子发现,eventfd 和 epoll 结合使用后,即使我将 flags 设置为 0 和上述执行的结果是一样的。这是为什么?因为按照 Looper.cpp 中的代码逻辑,分别对 epoll_wait 的返回值做了条件判断:

1.ret > 0 说明有可读的值,才会去从 eventfd 中去读;
2.ret == 0 说明超时,不会从 eventfd 中去读;
3.ret < 0 说明 epoll 异常,不会从 eventfd 中去读;

三、应用实例
Android Looper.cpp 的代码,使用 eventfd 和 epoll 这两个结合

# \system\core\libutils\Looper.cpp(Android 8.0 源码)
Looper::Looper(bool allowNonCallbacks) :mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {// 创建 eventfd 的句柄,返回该文件(Linux 中一切皆为文件)读写的描述符mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s",strerror(errno));AutoMutex _l(mLock);rebuildEpollLocked();
}void Looper::rebuildEpollLocked() {......// 创建一个 epoll 的句柄,EPOLL_SIZE_HINT 是指监听的描述符个数// 现在内核支持动态扩展,该值的意义仅仅是初次分配的 fd 个数,后面空间不够时会动态扩容。// 当创建完 epoll 句柄后,占用一个 fd 值.mEpollFd = epoll_create(EPOLL_SIZE_HINT);struct epoll_event eventItem;memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field unioneventItem.events = EPOLLIN;eventItem.data.fd = mWakeEventFd;// 对 mWakeEventFd 文件描述符进行注册,这样 mEpollFd 就能监听到 mWakeEventFd 的读写事件。int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);......
}int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKEALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif...... struct epoll_event eventItems[EPOLL_MAX_EVENTS];// 等待 mEpollFd 上的 IO 事件int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);......
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/254554.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

pyest+appium实现APP自动化测试

目录 01、appium环境搭建 2、搭建pythonpytestappium环境 3、安装pycharm搭建项目编写脚本 4、执行测试 绵薄之力 01、appium环境搭建 安装nodejs http://nodejs.cn/ 为什么要安装nodejs&#xff1f; 因为appium这个工具的服务端是由nodejs语言开发的 安装jdk&#x…

maxwell界面介绍

File文件 New新建&#xff08;快捷键CTRLN&#xff09; open打开&#xff08;选择open后可以选择各种可以打开的类型文件。包括其他软件CAD、老版本ansoft mxwl文件。以及Maxwell其他模块的文件。&#xff09; Open Examples打开范例&#xff08;与上效果相似&#xff0c;但是能…

Maxwell个人初学经验及资料分享

文章目录 前言一、原理重要二、官方教程三、视频教程三、帮助文档总结 前言 前段时间学习了课题组基于Maxwell所建立的模型&#xff0c;因为以前从来没有使用过&#xff0c;只是按照一步一步记忆了操作&#xff0c;但总觉得不熟悉&#xff0c;我想是缺乏对这个软件整体的了解&…

maxwell 平面变压器仿真(一)

目录 系列文章目录 前言 一、Maxwell能干什么&#xff1f; 二、Maxwell 安装与基本界面工具介绍 1.Maxwell安装 2.maxwell 基本界面工具介绍 maxwell界面如下&#xff1a; 菜单栏如下&#xff1a; 简要介绍如下&#xff1a; 3.以实例介绍一下布尔运算 前言 在学习开关电…

【Maxwell笔记】

官网地址&#xff1a;Maxwells Daemon 版本&#xff1a;v1.29.2&#xff08;之后的版本不支持JDK8&#xff09; 一、工作原理 Maxwell把自己伪装成MySQL的一个slave&#xff0c;然后以slave的身份假装从MySQL&#xff08;master&#xff09;复制数据。 MySQL配置binlog&…

ANSYS Maxwell 2D螺线管磁场分析

所用螺线管几何模型如下所示 1. 在Maxwell软件中建立如下图所示的几何模型 2. 设置材料 线圈采用铜材料copper&#xff0c;空气域自动为空气材料 3. 空气域边界设置磁通平行边界条件 4. 线圈施加电流激励&#xff0c;实际是安匝数 上半个线圈设置参数如下 其中参考方向Posit…

Maxwell-学习笔记

一、理论知识 1. 磁场的高斯定律 磁场对任意闭合曲面的通量都为0 2.安培环路定律 磁场沿不围绕长直导线的闭合曲线的环流为0,围绕长直导线的闭合曲线的环流为uI。 3. 磁化电流 磁介质内部的分子电流因为抵消而不参与整体磁化电流的贡献。 二、有限元方法 三.EM操作步骤 …

Maxwell安装与配置

Maxwell安装与配置 maxwell 是由美国zendesk开源&#xff0c;用java编写的Mysql实时抓取软件。其抓取的原理也是基于binlog。 点击此处跳转Maxwell官网 参考文章如下链接 链接: Maxwell参考文章 附上本人下载好的安装包 链接&#xff1a;百度网盘下载连接 提取码&#xff1a;12…

Maxwell简介使用

Maxwell 介绍 Maxwell 是由美国 zendesk 开源&#xff0c;用 java 编写的 Mysql 实时抓取软件&#xff0c; 其抓取的 原理也是基于 binlog。 官网 https://maxwells-daemon.io/ Maxwell 和 canal 工具对比 ➢ Maxwell 没有 canal 那种 serverclient 模式&#xff0c;只有一个…

Maxwell 一款简单易上手的实时抓取Mysql数据的软件

第一章 Maxwell概述 1.1、Maxwell简介 Maxwell 是由美国 Zendesk 开源&#xff0c;用 Java 编写的 MySQL 实时抓取软件。 实时读取MySQL 二进制日志 Binlog&#xff0c;并生成 JSON 格式的消息&#xff0c;作为生产者发送给 Kafka&#xff0c;Kinesis、RabbitMQ、Redis、Googl…

maxwell理论知识

1 Maxwell是什么 Maxwell 是由美国 Zendesk 开源&#xff0c;用 Java 编写的 MySQL 实时抓取软件。 实时读取 MySQL 二进制日志 Binlog&#xff0c;并生成 JSON 格式的消息&#xff0c;作为生产者发送给 Kafka&#xff0c;Kinesis、 RabbitMQ、Redis、Google Cloud Pub/Sub、文…

maxwell render中文版

教程&#xff1a; 1、下载软件包解压&#xff0c;双击exe程序安装软件&#xff0c;点击下一步。 2、软件协议界面&#xff0c;选择我接受协议&#xff0c;点击下一步。 3、选择安装路径&#xff0c;选择C盘之外的磁盘安装。 4、选择安装组件&#xff0c;全部勾选&#xff0…

Maxwell简单使用

1.Maxwell简介 Maxwell 是由美国 Zendesk 开源&#xff0c;用 Java 编写的 MySQL 实时抓取软件。 实时读取MySQL 二进制日志 Binlog&#xff0c;并生成 JSON 格式的消息&#xff0c;作为生产者发送给 Kafka&#xff0c;Kinesis、RabbitMQ、Redis、Google Cloud Pub/Sub、文件或…

Maxwell 介绍、安装、部署、运行(MYSQL -> Maxwell -> kafka)

Maxwell 安装、部署、运行&#xff08;MYSQL -> Maxwell -> kafka&#xff09; 一、Maxwell 概述 Maxwell 用 Java 编写的 MySQL 实时抓取软件。 实时读取MySQL 二进制日志 Binlog&#xff0c;并生成 JSON 格式的消息&#xff0c;作为生产者发送给 Kafka等消费中心。 …

Maxwell的简介与使用

一、简介 Maxwell 是由美国Zendesk公司开源&#xff0c;用Java编写的MySQL变更数据抓取软件。它会实时监控Mysql数据库的数据变更操作&#xff08;包括insert、update、delete&#xff09;&#xff0c;并将变更数据以 JSON 格式发送给 Kafka、Kinesi等流数据处理平台。 Maxwell…

推荐三款动态壁纸软件,足够让你的桌面惊艳!

点击上方“码农的后花园”&#xff0c;选择“星标” 公众号 精选文章&#xff0c;第一时间送达 今天给大家推荐三款动态壁纸软件&#xff0c;给你的桌面添加一点精彩,心情也更美呢 ~ 1&#xff0c;WinDynamicDesktop 进入软件界面&#xff0c;默认提供了5套苹果系统使用过的动态…

找壁纸不用愁了:壁纸网站/APP/工具合集

苏生不惑第261篇原创文章&#xff0c;将本公众号设为星标&#xff0c;第一时间看最新文章。 之前分享过那些好用的无版权免费图片网站&#xff0c;今天分享些好看的壁纸网站和工具&#xff0c;在公众号后台回复 壁纸 获取软件。 wallhaven 这个知名壁纸网站应该不用多介绍了htt…

免费壁纸网站大全

极简壁纸展开目录 https://bz.zzzmh.cn 国人维护的壁纸网站&#xff0c;图片大多来源 wallhaven 和 unsplash。 轻壁纸展开目录 一个免费纯净高清电脑壁纸分享站&#xff0c;主打高清横屏电脑壁纸&#xff0c;永久免费&#xff01; https://bz.qinggongju.com 壁纸湖展开目…

推荐两款超高质量的壁纸软件

前言 整天对着电脑工作难免会感觉烦躁&#xff0c;但如果能经常看到自己喜欢的壁纸&#xff0c;那应该会稍微愉悦一点吧… 不过搜索引擎找到的壁纸壁纸普遍清晰度低、带水印、质量也不高 每次还需要下载完手动替换&#xff0c;比较麻烦 今天给大家推荐两款高质量并且开源的…

高清免费壁纸网站推荐

本期内容&#xff0c;为大家整理了6个相当不错的免费壁纸网站&#xff0c;访问量极大、活跃度极高。 无需登录、注册&#xff0c;打开右键就可以下载&#xff0c;而且壁纸图片的尺寸大小&#xff0c;可以选择&#xff0c;从手机、平板、再到电脑壁纸&#xff0c;全部都是高清。…