1.网络编程如何检测硬件驱动层已连接
2.redis源码解读(一):事件驱动的驱动驱动io模型,为什么,线程线程是源码源码什么,怎么做
3.如何在Ubuntu 22.04.1 (Linux kernel 5.19)上安装摩尔线程S80驱动
4.Linux驱动开发头文件剖析(二十七):<linux/wait.h>
5.ZMQ源码详细解析 之 进程内通信流程
6.什么是驱动驱动事件驱动编程机制
网络编程如何检测硬件驱动层已连接
1、在桌面上右击“我的线程线程电脑”-“设备管理器”。在这里就能清晰的源码源码网站源码买卖看到,哪个驱动未安装或者未安装成功。驱动驱动
2、线程线程在桌面上右击“我的源码源码电脑”-“管理”。打开选项卡中的驱动驱动“设备管理器”,也可以看到。线程线程3、源码源码利用第三方驱动安装软件检测。驱动驱动
redis源码解读(一):事件驱动的线程线程io模型,为什么,源码源码是什么,怎么做
Redis作为一个高性能的内存数据库,因其出色的读写性能和丰富的数据结构支持,已成为互联网应用不可或缺的中间件之一。阅读其源码,可以了解其内部针对高性能和分布式做的种种设计,包括但不限于reactor模型(单线程处理大量网络连接),定时任务的实现(面试常问),分布式CAP BASE理论的实际应用,高效的数据结构的实现,其次还能够通过大神的代码学习C语言的编码风格和技巧,让自己的代码更加优雅。
下面进入正题:为什么需要事件驱动的io模型
我们可以简单地将一个服务端程序拆成三部分,接受请求->处理请求->返回结果,其中接收请求和处理请求便是我们常说的网络io。那么网络io如何实现呢,首先我们介绍最基础的io模型,同步阻塞式io,也是很多同学在学校所学的“网络编程”。
使用同步阻塞式io的单线程服务端程序处理请求大致有以下几个步骤
其中3,4步都有可能使线程阻塞(6也会可能阻塞,这里先不讨论)
在第3步,如果没有客户端请求和服务端建立连接,那么服务端线程将会阻塞。如果redis采用这种io模型,那主线程就无法执行一些定时任务,比如过期key的清理,持久化操作,集群操作等。
在第4步,如果客户端已经建立连接但是没有发送数据,服务端线程会阻塞。欢迎语音易语言源码若说第3步所提到的定时任务还可以通过多开两个线程来实现,那么第4步的阻塞就是硬伤了,如果一个客户端建立了连接但是一直不发送数据,服务端便会崩溃,无法处理其他任何请求。所以同步阻塞式io肯定是不能满足互联网领域高并发的需求的。
下面给出一个阻塞式io的服务端程序示例:
刚才提到,阻塞式io的主要问题是,调用recv接收客户端请求时会导致线程阻塞,无法处理其他客户端请求。那么我们不难想到,既然调用recv会使线程阻塞,那么我们多开几个几个线程不就好了,让那些没有阻塞的线程去处理其他客户端的请求。
我们将阻塞式io处理请求的步骤改造下:
改造后,我们用一个线程去做accept,也就是获取已经建立的连接,我们称这个线程为主线程。然后获取到的每个连接开一个新的线程去处理,这样就能够将阻塞的部分放到新的线程,达到不阻塞主线程的目的,主线程仍然可以继续接收其他客户端的连接并开新的线程去处理。这个方案对高并发服务器来说是一个可行的方案,此外我们还可以使用线程池等手段来继续优化,减少线程建立和销毁的开销。
将阻塞式io改为多线程io:
我们刚才提到多线程可以解决并发问题,然而redis6.0之前使用的是单线程来处理,之所以用单线程,官方给的答复是redis的瓶颈不在cpu,既然不在cpu那么用单线程可以降低系统的复杂度,避免线程同步等问题。如何在一个线程中非阻塞地处理多个socket,进而实现多个客户端的并发处理呢,那就要借助io多路复用了。
io多路复用是操作系统提供的另一种io机制,这种机制可以实现在一个线程中监控多个socket,返回可读或可写的socket,当一个socket可读或可写时再去操作它,这样就避免了对某个socket的阻塞等待。
将多线程io改为io多路复用:
什么是事件驱动的io模型(Reactor)
这里只讨论redis用到的单线程Reactor模型
事件驱动的io模型并不是一个具体的调用,而是高并发服务器的一种抽象的编程模式。
在Reactor模型中,有三种事件:
与这三种事件对应的,有三种handler,负责处理对应的永恒之蓝iapp源码事件。我们在一个主循环中不断判断是否有事件到来(一般通过io多路复用获取事件),有事件到来就调用对应的handler去处理时间。
听着玄乎,实际上也就这一张图:
事件驱动的io模型在redis中的实现
以下提及的源码版本为 5.0.8
文字的苍白的,建议参照本文最后的方法下载代码,自己调试下
整体框架
redis-server的main方法在 src/server.c 最后,在main方法中,首先进行一系列的初始化操作,最后进入进入Reactor模型的主循环中:
主循环在aeMain函数中,aeMain函数传入的参数 server.el ,是一个 aeEventLoop 类型的全局变量,保存了主循环的一些状态信息,包括需要处理的读写事件、时间事件列表,epoll相关信息,回调函数等。
aeMain函数中,我们可以看到当 eventLoop->stop 标志位为0时,while循环中的内容会被重复执行,每次循环首先会调用beforesleep回调函数,然后处理时间。beforesleep函数在main函数中被注册,会进行集群状态更新、AOF落盘等任务。
之所以叫beforesleep,是因为aeProcessEvents函数中包含了获取事件和处理事件的逻辑,其中获取读写事件时通过epoll_wait实现,会将线程阻塞。
在aeProcessEvents函数中,处理读写事件和时间事件,参数flags定义了需要处理的事件类型,我们可以暂时忽略这个参数,认为读写时间都需要处理。
aeProcessEvents函数的逻辑可以分为三个部分,首先获取距离最近的时间事件,这一步的目的是为了确定epoll_wait的超时时间,并不是实际处理时间事件。
第二个部分为获取读写事件并处理,首先调用epoll_wait,获取需要处理的读写事件,超时时间为第一步确定的时间,也就是说,如果在超时时间内有读写事件到来,那么处理读写时间,如果没有读写时间就阻塞到下一个时间事件到来,小白兔直播源码去处理时间事件。
第三个部分为处理时间事件。
事件注册与获取
上面我们讲了整体框架,了解了主循环的大致流程。接下来我们来看其中的细节,首先是读写事件的注册与获取。
redis将读、写、连接事件用结构aeFileEvent表示,因为这些事件都是通过epoll_wait获取的。
事件的具体类型通过mask标志位来区分。aeFileEvent还保存了事件处理的回调函数指针(rfileProc、wfileProc)和需要读写的数据指针(clientData)。
既然读写事件是通过epoll io多路复用实现,那么就避不开epoll的三部曲 epoll_create epoll_ctrl epoll_wait,接下来我们看下redis对epoll接口的封装。
我们之前提到aeMain函数的参数是一个 aeEventLoop 类型的全局变量,aeEventLoop中保存了epoll文件描述符和epoll事件。在aeApiCreate函数(src/ae_epoll.c)中,会调用epoll_create来创建初始化epoll文件描述符和epoll事件,调用关系为 main -> initServer -> aeCreateEventLoop -> aeApiCreate
调用epoll_create创建epoll后,就可以添加需要监控的文件描述符了,需要监控的情形有三个,一是监控新的客户端连接连接请求,二是监控客户端发送指令,也就是读事件,三是监控客户端写事件,也就是处理完了请求写回结果。
这三种情形在redis中被抽象为文件事件,文件事件通过函数aeCreateFileEvent(src/ae.c)添加,添加一个文件事件主要包含三个步骤,通过epoll_ctl添加监控的文件描述符,指定回调函数和指定读写缓冲区。
最后是通过epoll_wait来获取事件,上文我们提到,在每次主循环中,首先根据最近到达的时间事件来计算epoll_wait的超时时间,然后调用epoll_wait获取事件,再处理事件,其中获取事件在函数aeApiPoll(src/ae_epoll.c)中。
获取到事件后,主循环中会逐个调用事件的回调函数来处理事件。
读写事件的实现
写累了,有空补上……
如何使用vscode调试redis源码
编译出二进制程序
这一步有可能报错:
jemalloc是内存分配的一种更高效的实现,用于代替libc的仿爱卡汽车源码默认实现。这里报错找不到jemalloc,我们只需要将其替换成libc默认实现就好:
如果报错:
我们可以在src目录找到一个脚本名为mkreleasehdr.sh,其中包含创建release.h的逻辑,将报错信息网上翻可以发现有一行:
看来是这个脚本没有执行权限,导致release.h没有成功创建,我们需要给这个脚本添加执行权限然后重新编译:
2. 创建调试配置(vscode)
创建文件 .vscode/launch.json,并填入以下内容:
然后就可以进入调试页面打断点调试了,main函数在 src/server.c
如何在Ubuntu ..1 (Linux kernel 5.)上安装摩尔线程S驱动
摩尔线程为Ubuntu系统提供了显卡驱动,我购买了S显卡并安装在Ubuntu ..1系统上。在安装过程中,我遇到了内核模块编译失败的问题,原因是Linux内核的DMA-BUF API发生变化。经过分析,我发现驱动代码主要在两个方面需要修改:mtgpu-1.0.0/src/pvr/pvr_buffer_sync.c文件中的管理内核态buffer及同步的函数。
为了修复这个问题,我下载了补丁文件mtdrv-fix.patch,并在root账户下使用以下命令将补丁应用到源代码中:
之后,我重新编译了内核,至此问题得以解决。重启电脑后,系统获得了高清分辨率,可以通过执行mthreads-gmi验证驱动的正确性。
该文章是在使用S显卡的Ubuntu机器上发布的,目前使用过程中稳定性良好,但需要长时间的测试以确保稳定性。当前版本的xorg驱动代码非开源,与Ubuntu的xorg ABI不匹配,因此没有硬件加速,仅支持kms模式。
Linux驱动开发头文件剖析(二十七):<linux/wait.h>
wait.h》主要用于进程同步和等待队列的管理,提供宏和函数实现内核中等待队列机制,支持进程在条件满足前进入睡眠状态,被唤醒后继续执行。这一机制广泛应用于并发、同步和事件等待场景,是驱动开发和内核模块编写中常见工具。
PS1:睡眠与等待在特定情况下等同,需谨慎辨识。
PS2:宏或函数功能通常从名称可大致推断,具体用法查阅文档。
wait_queue_entry_t 类型是 wait_queue_entry 的别名,wait_queue_func_t 指向四个参数、返回 int 的函数指针。wait_queue_entry 结构体定义了等待队列中的每个节点,包括进程状态、唤醒函数等字段。
通用唤醒函数用于尝试唤醒等待队列中的进程。
该函数实现于相应位置。
定义了用于描述等待队列项状态和行为的标志位。
wait_queue_head_t 是 struct wait_queue_head 的别名,用于实现等待队列机制,包含自旋锁和链表头,保护和管理等待队列。
struct task_struct 的声明在 wait.h 内,具体实现位于相应位置,与调度相关讨论时会涉及。
__WAITQUEUE_INITIALIZER 初始化 wait_queue_entry 结构体,根据给定的 name 和 task,将其 private 指针指向进程结构体 tsk,并设置默认唤醒函数和空的前后指针。
DECLARE_WAITQUEUE 是 __WAITQUEUE_INITIALIZER 的简化封装,得到 wait_queue_entry 变量。
__WAIT_QUEUE_HEAD_INITIALIZER 初始化等待队列头,设置锁字段和队列头字段。
DECLARE_WAIT_QUEUE_HEAD 是 __WAIT_QUEUE_HEAD_INITIALIZER 的简化封装。
__init_waitqueue_head 初始化 wq_head 的各项属性。
init_waitqueue_head 在 __init_waitqueue_head 的基础上增加用于锁调试的变量。
__init_waitqueue_head_onstack 初始化等待队列头,用于栈上分配。
DECLARE_WAITQUEUE_HEAD_ONSTACK 是 __init_waitqueue_head_onstack 的简化封装。
init_waitqueue_entry 初始化等待队列节点,将唤醒函数设置为默认唤醒函数。
init_waitqueue_func_entry 与 init_waitqueue_entry 类似,通过给定的唤醒函数进行进程唤醒。
waitqueue_active 无锁地检查等待队列是否为空,返回布尔值指示等待队列状态。
PS:使用时需小心数据竞争。
判断等待队列中是否有唯一等待进程。
检查给定等待队列中有无等待进程。
这三个函数与等待队列机制相关,用于添加、移除等待队列中的等待项。
__add_wait_queue 将等待项添加到等待队列中,通常为头部。
__add_wait_queue_exclusive 通过设置独占标志将等待项添加,确保独占等待。
__add_wait_queue_entry_tail 将等待项添加到队列尾部。
__add_wait_queue_entry_tail_exclusive 与 __add_wait_queue_entry_tail 类似,设置独占标志。
__remove_wait_queue 从等待队列中移除等待项。
这些宏用于非阻塞机制下唤醒等待队列中的任务,实现 I/O 多路复用等功能。
wake_up_pollfree 通知等待队列即将被销毁,用于非阻塞轮询处理。
___wait_cond_timeout 检查条件并修改返回值。
___wait_is_interruptible 检查任务状态是否可中断。
init_wait_entry 初始化等待队列条目。
___wait_event 实现等待队列机制,支持不同等待策略。
__wait_event、wait_event、might_sleep、__io_wait_event、__wait_event_freezable、__wait_event_timeout、__wait_event_exclusive_cmd、__wait_event_interruptible、__wait_event_killable、__wait_event_freezable_exclusive 等宏,用于实现各种等待队列操作。
__wait_event_interruptible_timeout、__wait_event_hrtimeout、wait_event_interruptible_hrtimeout 等宏,提供超时等待功能。
__wait_event_idle、wait_event_idle_exclusive、wait_event_idle_timeout、__wait_event_idle_exclusive_timeout 等宏,支持 TASK_IDLE 状态下等待。
do_wait_intr、do_wait_intr_irq 提供等待队列支持,并允许在等待时被中断。
__wait_event_killable、__wait_event_killable_exclusive 宏用于 TASK_KILLABLE 状态下的等待。
__wait_event_killable_timeout、wait_event_killable_timeout 宏提供超时等待功能。
这些函数和宏用于处理等待队列,支持线程在条件满足前进入睡眠状态,直到被唤醒。
init_wait 初始化已经存在的 wait_queue_entry 结构体。
try_invoke_on_locked_down_task 尝试在已锁定任务上调用函数,确保操作安全。
ZMQ源码详细解析 之 进程内通信流程
ZMQ进程内通信流程解析
ZMQ的核心进程内通信原理相当直接,它利用线程间的两个队列(我称为pipe)进行消息交换。每个线程通过一个队列发送消息,从另一个队列接收。ZMQ负责将pipe绑定到对应线程,并在send和recv操作中通过pipe进行数据传输,非常简单。
我们通过一个示例程序来理解源码的工作流程。程序首先创建一个简单的hello world程序,加上sleep是为了便于分析流程。程序从`zmq_ctx_new()`开始,这个函数创建了一个上下文(context),这是ZMQ操作的起点。
在创建socket时,如`zmq_socket(context, ZMQ_REP)`,实际调用了`ctx->create_socket`,socket类型决定了其特性。rep_t是基于router_t的特化版本,主要通过限制router_t的某些功能来实现响应特性。socket的创建涉及到诸如endpoint、slot和 mailbox等概念,它们在多线程环境中协同工作。
进程内通信的建立通过`zmq_bind(responder, "inproc://hello")`来实现,这个端点被注册到上下文的endpoint集合中,便于其他socket找到通信通道。zmq的优化主要集中在关键路径上,避免对一次性操作过度优化。
接下来的recv函数是关键,即使没有连接,它也会尝试接收消息。`xrecv`函数根据进程状态可能阻塞或返回EAGAIN。recv过程涉及`msg_t`消息的处理,以及与`signaler`和`mailbox`的交互,这些组件构成了无锁通信的核心。
发送端通过`connect`函数建立连接,创建连接通道,并将pipe关联到socket。这个过程涉及无锁队列的管理,如ypipe_t和pipe_t,以及如何均衡发送和接收。
总结来说,ZMQ进程内通信的核心是通过管道、队列和事件驱动机制,实现了线程间的数据交换。随着对ZMQ源码的深入,会更深入理解这些基础组件的设计和工作原理。
什么是事件驱动编程机制
1、要理解事件驱动和程序,就需要与非事件驱动的程序进行比较。实际上,现代的程序大多是事件驱动的,比如多线程的程序,肯定是事件驱动的。早期则存在许多非事件驱动的程序,这样的程序,在需要等待某个条件触发时,会不断地检查这个条件,直到条件满足。而事件驱动的程序,则有机会释放cpu从而进入睡眠态,当事件触发时被操作系统唤醒,这样就能更加有效地使用cpu。
2、一个典型的事件驱动的程序,就是一个死循环,并以一个线程的形式存在,这个死循环包括两个部分,第一个部分是按照一定的条件接收并选择一个要处理的事件,第二个部分就是事件的处理过程。程序的执行过程就是选择事件和处理事件,而当没有任何事件触发时,程序会因查询事件队列失败而进入睡眠状态,从而释放cpu。
3、事件驱动的程序,必定会直接或者间接拥有一个事件队列,用于存储未能及时处理的事件。
4、事件驱动的程序的行为,完全受外部输入的事件控制,所以,事件驱动的系统中,存在大量这种程序,并以事件作为主要的通信方式。
5、事件驱动的程序,还有一个最大的好处,就是可以按照一定的顺序处理队列中的事件,而这个顺序则是由事件的触发顺序决定的,这一特性往往被用于保证某些过程的原子化。
Framework层的Binder(源码分析篇)
本文以android-.0.0_r的AOSP分支为基础,解析framework层的Binder工作原理。
从ServiceManager的getService方法入手,其核心代码是通过getIServiceManager().getService(name)获取服务。首先,ServiceManager的实现与进程中的ProcessState密切相关,ProcessState是单例,负责打开和映射Binder驱动。构造函数中,它会初始化驱动、验证版本并设置线程数,接着进行binder映射。
在ProcessState的getContextObject方法中,调用native函数android_util_Binder.cpp中的getContextObject()。这个函数通过handle 0(ServiceManager的handle)获取BpBinder对象,然后通过javaObjectForIBinder函数将其转换为Java中的类型。
进一步分析,BpBinder与java层的Binder之间存在对应关系,通过BinderProxy NativeData创建单例的BinderProxy。然后,每个服务的BinderProxy实例化和计数处理都在这个过程中完成。ServiceManagerNative.asInterface方法简化了getIServiceManager的调用,通过调用asInterface实例化ServiceManagerProxy。
IServiceManager接口通过AIDL生成,其代理类ServiceManagerProxy实际上是不必要的。aidl文件在编译时生成对应java代码,用于binder通信。通过aidl文件,我们可以看到如queryLocalInterface等方法的实现细节。
在Parcel的协助下,客户端与服务端进行数据传递,通过序列化和反序列化进行交互。在transact函数中,对Parcel大小进行检查,避免数据传输过大导致的问题。最后,客户端与binder驱动的通信过程涉及了Transaction数据的写入、等待响应、数据处理和内存回收等步骤。
总的来说,framework层的Binder工作涉及服务管理、数据转换、通信协议和内存管理等环节,理解这些有助于深入掌握Binder的工作机制。