eventfd 和 epoll 的结合使用

news2024/12/28 12:40:06

一.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 14
  Child writing 1 to efd
  Child writing 2 to efd
  Child writing 4 to efd
  Child writing 7 to efd
  Child writing 14 to efd
  Child completed write loop
  Parent about to read
  Parent 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 union
    eventItem.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_WAKE
    ALOGD("%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://www.coloradmin.cn/o/591360.html

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

相关文章

spring cloud Alibaba之Nacos Discovery--服务治理 (二)

接着上一篇文章 搭建的微服务环境, 实现nacos 注册中心实战操作案例 一. 服务治理介绍 先来思考一个问题 通过上一章的操作&#xff0c;我们已经可以实现微服务之间的调用。但是我们把服务提供者的网络地址 &#xff08;ip&#xff0c;端口&#xff09;等硬编码到了代码中&a…

【SpringCloud】SpringAMQP

文章目录 1、AMQP2、基本消息模型队列3、WorkQueue模型4、发布订阅模型5、发布订阅-Fanout Exchange6、发布订阅-DirectExchange7、发布订阅-TopicExchange 1、AMQP Advanced Message Queuing Protocol&#xff0c;高级消息队列协议。是用于在应用程序之间传递业务消息的开放标…

ElasticSearch安装部署——超详细

ElasticSearch安装部署 简介 全文搜索属于最常见的需求&#xff0c;开源的 Elasticsearch &#xff08;以下简称 es&#xff09;是目前全文搜索引擎的首选。 它可以快速地储存、搜索和分析海量数据。维基百科、Stack Overflow、Github 都采用它。 Elasticsearch简称es&…

JS中手撕防抖函数和节流函数

1.防抖函数 1.1定义 说明&#xff1a;在一定时间内&#xff0c;频繁执行事件&#xff0c;只执行最后一次函数。(英雄联盟回城) 1.2步骤&#xff1a; 声明定时器函数判断是否有定时器函数&#xff0c;如果有定时器函数的话就清除定时器。。如果没有定时器函数的话&#xff0…

chatgpt赋能python:Python中的三角函数介绍

Python中的三角函数介绍 Python作为一种高级编程语言&#xff0c;可以处理基础算术运算、三角函数等高等数学的操作。其中&#xff0c;三角函数是常用的数学函数之一&#xff0c;Pyhon中的三角函数包括正弦函数、余弦函数、正切函数等。 正弦函数 正弦函数在三角学中是最基本…

chatgpt赋能python:精度问题在Python编程中的影响及解决方法

精度问题在Python编程中的影响及解决方法 Python是一种解释性编程语言&#xff0c;以其简单易学、开发效率高等特点而广受欢迎。然而&#xff0c;Python中的浮点数精度问题却经常困扰着程序员。在本文中&#xff0c;我们将详细介绍Python中精度问题的影响及解决方法。 精度问…

华为OD机试真题B卷 Java 实现【字符串分隔】,附详细解题思路

一、题目描述 输入一个字符串&#xff0c;请按长度为8拆分每个输入字符串并进行输出&#xff0c;长度不是8整数倍的字符串请在后面补数字0&#xff0c;空字符串不处理。 二、输入描述 连续输入字符串(每个字符串长度小于等于100)。 三、输出描述 依次输出所有分割后的长度…

layui框架学习(25:弹出层模块_加载框询问框)

layui框架的弹出层模块layer中最重要的函数即layer.open&#xff0c;基于该函数&#xff0c;layer模块封装了很多常用弹出框&#xff0c;上文已介绍了消息框和提示框函数&#xff0c;本文学习加载框和询问框函数的基本用法&#xff0c;同时继续学习layer模块中基础参数的用法。…

Part1:使用 TensorFlow 和 Keras 的 NeRF计算机图形学和深度学习——计算机图形学世界中相机的工作原理

Part1&#xff1a;使用 TensorFlow 和 Keras 的 NeRF计算机图形学和深度学习 1. 效果图2. 原理2.0 前向成像模型2.1 世界坐标系2.2 相机坐标系2.3 坐标变换2.4 投影转换2.5 数据 3. 源码参考 是否有一种方法可以仅从一个场景多张不同视角的照片中捕获整个3D场景&#xff1f; 有…

有奖励!2023陕西省首台(套)重大技术装备产品项目申报条件、认定材料

本文整理了2023陕西省首台&#xff08;套&#xff09;重大技术装备产品项目申报条件&#xff0c;认定材料等相关内容&#xff0c;感兴趣的朋友快跟小编一起来看看吧&#xff01; 一、重点支持方向及领域 重点支持方向及领域&#xff1a;高档工业母机、电力装备、大型矿山和冶金…

【MySQL】MySQL 字段为 NULL 的5大坑

数据准备 建立一张表 数据如下&#xff1a; 1.count 数据丢失 count(*) 会统计值为 NULL 的行&#xff0c;而 count(列名) 不会统计此列为 NULL 值的行。 select count(*),count(name) from person; ----------------------- count(*) | count(name)10 | 8-------…

如何使用Python自动化测试工具Selenium进行网页自动化?

引言 Selenium是一个流行的Web自动化测试框架&#xff0c;它支持多种编程语言和浏览器&#xff0c;并提供了丰富的API和工具来模拟用户在浏览器中的行为。Selenium可以通过代码驱动浏览器自动化测试流程&#xff0c;包括页面导航、元素查找、数据填充、点击操作等。 与PyAuto…

关于linux的ssh(出现的问题以及ubuntu的ssh配置和ssh连接超时问题)

目录 Ubuntu进行ssh连接 关于ssh报错排错 备注&#xff1a;防火墙和selinux可能对ssh连接存在限制&#xff0c;但是我在操作的时候并没对我照成影响 查看selinux状态 ssh_config和sshd_config的区别 Ubuntu进行ssh连接 1.首先需要安装SSH服务器&#xff0c;在ubuntu终端输…

Java学习路线(19)——IO流(下)

一、缓冲流 1、概念&#xff1a; 一种自带缓冲区的字节流、可提高原始字节流、字符流读写数据的性能。 2、缓冲流高性能原理&#xff1a; 磁盘与内存之间有一块存储区域&#xff0c;当磁盘向内存传输数据时&#xff0c;先传输到缓冲区&#xff0c;当缓冲区满了之后&#xff0…

[强网杯 2019]随便注 1【SQL注入】解析过程

1.首先启动并访问靶机&#xff0c;有一个输入框&#xff0c;随便输入1 or 1 1&#xff0c;测试一下是否存在sql注入。 2.提交后提示error 1064 : You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syn…

chatgpt赋能python:Python中的_--了解这个神秘的下划线

Python中的_ – 了解这个神秘的下划线 Python是一种流行的编程语言&#xff0c;它具有简单易学的语法和强大的功能。一些Python的特殊语法经常会让初学者感到困惑。其中&#xff0c;一个神秘的下划线符号在Python中出现的频率非常高&#xff0c;而且它的含义和使用也非常多样化…

chatgpt赋能python:Python中符号的用法

Python中符号的用法 在Python编程中&#xff0c;符号是非常重要的一部分。通过合理使用符号&#xff0c;我们可以轻松地实现许多功能和操作。下面是Python中一些常用的符号的介绍和用法。 赋值符号 斜杠等于号&#xff08;&#xff09;被用来赋值。例如&#xff0c;如果我们要…

Windows的Powershell终端增强

Ubuntu上一直用的Oh My Zsh强化终端&#xff0c;体验非常nice。最近在Win上做东西比较多&#xff0c;于是也想把Powershell这个简陋的终端加强一下。 说干就干&#xff0c;网上查了一圈&#xff0c;发现大部分人用Oh My Posh来操作&#xff0c;因此试了一下&#xff0c;发现卡…

UFS 1-UFS架构简介1

UFS 1-UFS架构简介 1 UFS是什么&#xff1f;1.1 UFS1.2 一般特征1.2.1 Target performance1.2.2 Target host applications1.2.3 Target device types1.2.4 Topology1.2.5 UFS Layering 1.3 Interface Features1.3.1 Three power supplies1.3.2 Signaling as defined by [MIPI-…

Linux系统下imx6ull QT编程—— C++类和对象(三)

Linux QT编程 文章目录 Linux QT编程一、类和对象 一、类和对象 C 在 C 语言的基础上增加了面向对象编程&#xff0c;C 支持面向对象程序设计。类是 C 的核心特性&#xff0c;通常被称为用户定义的类型。类用于指定对象的形式&#xff0c;它包含了数据表示法和用于处理数据的方…