epoll怎么就高效了?

目录

摘要

1 举个栗子

2 从 epoll_create 开始

3 epoll_ctl,插入待监听的描述符

3.1 故事围绕 ep_item 展开

3.2 在 socket 等待队列上设置 epoll 回调

3.3 关系变得复杂

 4 epoll_wait 等你

4.1 等待就绪事件

4.2 共享内存?

5 来了来了,数据来了

5.1 tcp_rcv_established

5.2 ep_poll_callback

5.3 唤醒任务

6 总结


摘要

        IO 多路复用这个词听起来还是蛮高大上的,在 Linux 上,常见的IO 多路复用的实现方案有 select、poll、epoll,相关的文章也都看过许多,都知道 epoll 高效,今天从内核源码角度学习一下 epoll 的高效之处。

文中引用 Linux 内核源码基于版本 2.6.34,并做了一些删减以提高可读性。

 

1 举个栗子

        想要更好的了解源码,得先搞清楚入口是哪个。Linux 提供了几个基本的接口供用户使用:

    // 创建一个 epoll 对象
    ep_fd = epoll_create(...);
    // 向 epoll 对象添加需要监听的 socket 文件描述符
    epoll_ctl(ep_fd, EPOLL_CTL_ADD, sock_fd, ...);
    // wait 轮询
    epoll_wait(ep_fd, ready_events)
    // 遍历处理就绪的事件
    for (ready_events)
        do_thing()

2 从 epoll_create 开始

        epoll_create 系统调用实现位于 fs/epollevent.c 下:

// 创建一个 epoll 对象
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
	struct eventpoll *ep = NULL;

	// 创建内部数据结构 ("struct eventpoll").
	error = ep_alloc(&ep);
	if (error < 0)
		return error;
	
    // 创建 epoll 的文件 inode 节点:ep 作为 file_struct 的一个 private_data
	error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep,
				 O_RDWR | (flags & O_CLOEXEC));
    ...
}

        epoll_create 这个方法比较简单,主要是调用 ep_alloc 创建了 eventpoll 这个结构体并为之关联具体的文件 inode 节点。可见 Linux 一切皆文件思想的应用到处都是。那么 eventpoll 长什么样子呢?

        event_poll 结构体中成员还是挺多的,这里只列出关键的几个变量:

struct eventpoll {
	// epoll_wait 使用的等待队列:文件事件就绪的时候,会通过这个队列来查找等待的进程
	wait_queue_head_t wq;

	// 就绪的文件描述符队列
	struct list_head rdllist;

	// 用来存储被监听文件描述符的红黑树:支持log(n)的查找、插入、删除
	struct rb_root rbr;
};

        ep_alloc 中负责对其分配内存及初始化:

static int ep_alloc(struct eventpoll **pep)
{
	struct eventpoll *ep;
	ep = kzalloc(sizeof(*ep), GFP_KERNEL);

	// 初始化等待队列
	init_waitqueue_head(&ep->wq);
	
    // 初始化就绪队列
	INIT_LIST_HEAD(&ep->rdllist);

    // 初始化被监听文件节点的结构->红黑树
	ep->rbr = RB_ROOT;

	*pep = ep;
}

         所以我们对 epoll_event 的初映像就可以画出来了:

        脑海中有了这个图,也有助于我们理解后面其它部分的逻辑了。 

3 epoll_ctl,插入待监听的描述符

        同样的,epoll_ctl 系统调用也是位于 fs/eventpoll.c 文件中,为了简化逻辑,我们只关心 ADD 操作:

SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
		struct epoll_event __user *, event)
{
	struct file *file, *tfile;
	struct eventpoll *ep;
	struct epitem *epi;
	struct epoll_event epds;

	// 从用户空间复制要监听的文件描述符及关心的事件类型
	if (ep_op_has_event(op) &&
	    copy_from_user(&epds, event, sizeof(struct epoll_event)))
		goto error_return;

	// 获取 epfd 关联的文件结构
	file = fget(epfd);
	// 获取目标文件的 strcut file 结构
	tfile = fget(fd);

	// 被监听的文件描述符必需支持 poll 方法,普通文件是不支持的
	if (!tfile->f_op || !tfile->f_op->poll)
		goto error_tgt_fput;

	// 通过 epfd 的文件结构中的私有数据部分拿到 eventpoll 结构
	ep = file->private_data;

	// 尝试查找目标 fd 对应的 ep_item
	epi = ep_find(ep, tfile, fd);

	switch (op) {
	case EPOLL_CTL_ADD:
        // 没有找到才需要执行插入操作
		if (!epi) {
			error = ep_insert(ep, &epds, tfile, fd);
		}
		break;
	case EPOLL_CTL_DEL:
		...
	case EPOLL_CTL_MOD:
		...
	}
}

3.1 故事围绕 ep_item 展开

        在 epoll_ctl 中,主要是通过 fd、epfd 找到对应的文件结构,然后执行 ep_insert 做真正的插入工作:

static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
		     struct file *tfile, int fd)
{
	struct epitem *epi;
	struct ep_pqueue epq;

    // 从 slab 对象分配器中分配一个 epitem 对象
	if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
		return -ENOMEM;

	// 初始化 epitem
    // 用于将 epitem 链接到就绪列表上
	INIT_LIST_HEAD(&epi->rdllink);
    // 一个包含 poll 等待队列的列表
    // 每个等待队列代表一个等待文件描述符状态变化的进程或线程
    // 当进程或线程使用 epoll_wait 时,会将自己添加到与 epitem 关联的等待队列中
	INIT_LIST_HEAD(&epi->pwqlist);
	epi->ep = ep;
    // 把目标文件 fd 和 struct file 保存在 ffd 中
	ep_set_ffd(&epi->ffd, tfile, fd);
	epi->event = *event;

	// 初始化 poll_table,说是表,其实只存放了一个函数指针:ep_ptable_queue_proc
	epq.epi = epi;
	init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);

	// 将 ep_poll_callback 设置到 socket 的等待队列,如果 socket 事件就绪,会回调它
	revents = tfile->f_op->poll(tfile, &epq.pt);

	// 将 epitem 插入红黑树
	ep_rbtree_insert(ep, epi);

	// 如果文件已经就绪,将其放入就绪列表中
	if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
		list_add_tail(&epi->rdllink, &ep->rdllist);
        if (waitqueue_active(&ep->poll_wait))
			pwake++;
	}

	// 唤醒等待的进程/线程
	if (pwake)
		ep_poll_safewake(&ep->poll_wait);
}

3.2 在 socket 等待队列上设置 epoll 回调

      将 ep_poll_callback  设置到 socket 的等待队列,调用的是 tfile->f_op->poll 方法,实际对应 socket 的 sock_poll 函数,该函数位于 net/socket.c 中:

static unsigned int sock_poll(struct file *file, poll_table *wait)
{
	struct socket *sock;
	sock = file->private_data;
	return sock->ops->poll(file, sock, wait);
}

        sock_poll 只是通过 struct file 的 private_data 获取到实际的 struct socket 结构,继续调用 scoket 中的 poll 回调。此处的 poll 方法就是 tcp_poll 了(如果是 UDP,则为 udp_poll ),位于 net/ipv4/tcp.c 中:

// 等待 tcp 事件
unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
{
	struct sock *sk = sock->sk;
	struct tcp_sock *tp = tcp_sk(sk);

    // sk_sleep 是 socket 上等待队列的列表头:wait_queue_head_t	*sk_sleep; 
    // 注意:不是 epoll 的等待队列
	sock_poll_wait(file, sk->sk_sleep, wait);

    // 下面是对 TCP 特殊状态的一些检查:如 connect、listen 状态
    ...
}

        对于普通的用来读写的 socket ,这里继续调用 sock_poll_wait 方法,将等待队列的列表头、poll_table 传给了 sock_poll_wait 。sock_poll_wait 是各静态方法,位于 include/net/sock.h :

static inline void sock_poll_wait(struct file *filp,
		wait_queue_head_t *wait_address, poll_table *p)
{
	if (p && wait_address) {
		poll_wait(filp, wait_address, p);
		smp_mb();
	}
}

        sock_poll_wait 主要是做了一层封装,在 poll_wait 后放了一道内存栅栏,避免编译优化指令乱序可能引发的问题,还是要看下 poll_wait 怎么做的:

// include/linux/poll.h
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
	if (p && wait_address)
        // 终于用到 poll_table 了,qproc 是前面保存的函数指针:ep_ptable_queue_proc
		p->qproc(filp, wait_address, p);
}

        回过头再去看 ep_ptable_queue_proc 的实现,现在输入参数 file 是目标 socket 对应的文件结构,whead 是目标 socket 上的等待队列列表头,pt 是我们的轮询表:

// fs/eventpoll.c
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
				 poll_table *pt)
{
	struct epitem *epi = ep_item_from_epqueue(pt);
	struct eppoll_entry *pwq;

	if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
        // scoket 等待队列的回调对象有自己的格式,这里需要初始化一下,把 ep_poll_callback 塞进去
		init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
		pwq->whead = whead;
		pwq->base = epi;
        // 真正把回调对象挂到 socket 的等待队列上了
		add_wait_queue(whead, &pwq->wait);
        ...
	}
    ...
}

        说了那么久的 ep_poll_callback 终于出现了,在 poll_table 回调中将其注册到了 socket 的等待队列中,后面 socket 事件就绪时,就可以调用 ep_poll_callback 来通知 epoll 了。

3.3 关系变得复杂

        所以 ep_ctl 里内核主要干了三件事:

  1. 为待监听的描述符分配红黑树节点:epitem
  2. 在 socket 等待队列中添加发生事件时的回调函数:ep_poll_callback
  3. 将 epitem 插进红黑树中

        此时,struct eventpoll 和 struct socket 开始纠缠不清了

 4 epoll_wait 等你

:啥时候到 epoll_wait 啊?

epoll_wait: 这就来了

4.1 等待就绪事件

        epoll_wait 做的事情比较简单,它会查看是否由就绪的事件发生,有就直接返回啦,没有就只能等会啦~

// pos: fs/eventpoll.c
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
		int, maxevents, int, timeout)
{
	struct file *file;
	struct eventpoll *ep;

	// 最多支持监听 INT_MAX / 16 个事件
	if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
		return -EINVAL;

	...
	error = ep_poll(ep, events, maxevents, timeout);
}

        epoll_wait 主要做了一些用户参数的检查,及获取 ep 对象等,核心逻辑在 ep_poll 中:

static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
		   int maxevents, long timeout)
{
	wait_queue_t wait;

	if (list_empty(&ep->rdllist)) {
        // 当前并没有就绪事件,这里需要将当前进程调度出去,等待 ep_poll_back 回调唤醒
        // 初始化等待队列项,关联了当前进程,current 是当前进程的 task_struct
		init_waitqueue_entry(&wait, current);
		wait.flags |= WQ_FLAG_EXCLUSIVE;
        // 添加等待队列项到 epoll 的等待队列
		__add_wait_queue(&ep->wq, &wait);

		for (;;) {
            // 设置为不可中断状态再检查一次是否有就绪事件,避免上次检查后被调度出去丢失通知
			set_current_state(TASK_INTERRUPTIBLE);
			if (!list_empty(&ep->rdllist) || !jtimeout)
				break;
			if (signal_pending(current)) {
				res = -EINTR;
				break;
			}

            // 主动 schedule 让出 CPU			
			jtimeout = schedule_timeout(jtimeout);
		}
		__remove_wait_queue(&ep->wq, &wait);

		set_current_state(TASK_RUNNING);
	}
	
    // 检查是否有事件就绪
	eavail = !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR;

	// 如果已经有就绪的事件,就将事件传输到用户空间
	if (!res && eavail &&
	    !(res = ep_send_events(ep, events, maxevents)) && jtimeout)
		goto retry;

	return res;
}

4.2 共享内存?

        如果有就绪事件,就调用 ep_send_events 方法将就绪事件通过某种方法传递到用户空间。网上有些文章说这里使用了共享内存以优化性能,真的有共享内存吗?看一下 ep_send_events 的实现就知道了:

pos: fs/eventpoll.c
static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head,
			       void *priv)
{
	for (eventcnt = 0, uevent = esed->events;
	     !list_empty(head) && eventcnt < esed->maxevents;) {
		epi = list_first_entry(head, struct epitem, rdllink);

		revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) &
			epi->event.events;

		if (revents) {
            // 拷贝就绪事件到用户空间
			if (__put_user(revents, &uevent->events) ||
			    __put_user(epi->event.data, &uevent->data)) {
				list_add(&epi->rdllink, head);
				return eventcnt ? eventcnt : -EFAULT;
			}
		}
	}

	return eventcnt;
}

        看到了吧,ep_send_events 内部是通过 __put_user 这个方法将数据拷贝到用户空间,并不涉及共享内存的使用(截止今日看 Linux master 分支最新代码,也仍然是使用这个方法进行拷贝)。所以这个事是个谣言!源码面前,无所遁形

5 来了来了,数据来了

        此时需要再把前面的这张图祭出来了:

        前面已经讲了,epoll_ctl 执行 ADD 的时候, 把 ep_poll_callback 放到了 socket 的等待队列上,socket 上事件就绪的话,就会回调这个函数,具体来看一下吧。

5.1 tcp_rcv_established

        协议栈收包流程处理逻辑还是比较复杂的,这里就不逐个展开看了,我们从收包之后的核心流程 tcp_rcv_established 看起:

// pos: net/ipv4/tcp_input.c
int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
			struct tcphdr *th, unsigned len)
{
	struct tcp_sock *tp = tcp_sk(sk);

    // 将 skb 入到 socket 接受队列
	__skb_pull(skb, tcp_header_len);
	__skb_queue_tail(&sk->sk_receive_queue, skb);

    // ...这里有一些用滑动窗口的处理

    // 数据就绪的处理
	sk->sk_data_ready(sk, 0);
	return 0;
}

        在 tcp_rcv_established 中,数据包已经放在 socket 的就绪队列了,然后执行 sk_data_ready ,sk_data_ready 实际是个函数指针,指向 sock_def_readable :

// pos: net/core/sock.c
static void sock_def_readable(struct sock *sk, int len)
{
    // 判断 socket 的等待队列是否为空
	if (sk_has_sleeper(sk))
        // 执行等待队列上的回调
		wake_up_interruptible_sync_poll(sk->sk_sleep, POLLIN | POLLRDNORM | POLLRDBAND);
    // 通知异步 IO
	sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);
}

        执行 ep_poll_back 的核心流程在 wake_up_interruptible_sync_poll 里,其它的暂不需要关注。继续跟踪它就好。wake_up_interruptible_sync_poll 是个宏方法,内部调的是 __wake_up_sync_key :

// pos: kernel/sched.c
void __wake_up(wait_queue_head_t *q, unsigned int mode,
			int nr_exclusive, void *key)
{
	unsigned long flags;

	spin_lock_irqsave(&q->lock, flags);
	__wake_up_common(q, mode, nr_exclusive, 0, key);
}

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
			int nr_exclusive, int wake_flags, void *key)
{
	wait_queue_t *curr, *next;

    // 遍历 socket 等待队列中的元素,并依次执行其 func 函数,即 ep_poll_callback
	list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
		unsigned flags = curr->flags;

		if (curr->func(curr, mode, wake_flags, key) &&
				(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
			break;
	}
}

        可以看到,收到数据后,最终会依次执行 socket 等待队列中设置的回调方法,其中就有 epoll_ctl 执行 ADD 时添加的 ep_poll_callback 回调方法。

5.2 ep_poll_callback

        数据包接收的上下文实际处于软中断上下文,所以 ep_poll_callback 也是在软中断上下文中执行的:

// pos: fs/eventpoll.c
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	struct epitem *epi = ep_item_from_wait(wait);
	struct eventpoll *ep = epi->ep;

	// 将就绪文件描述符对应的 epitem 对象放入 epoll 的就绪列表
	if (!ep_is_linked(&epi->rdllink))
		list_add_tail(&epi->rdllink, &ep->rdllist);

	/*
	 * Wake up ( if active ) both the eventpoll wait list and the ->poll()
	 * wait list.
	 */
    // 查看 epoll等待队列是否非空,是的话需要唤醒等待的任务
	if (waitqueue_active(&ep->wq))
		wake_up_locked(&ep->wq);
	if (waitqueue_active(&ep->poll_wait))
		pwake++;

    // 唤醒操作
	if (pwake)
		ep_poll_safewake(&ep->poll_wait);

	return 1;
}

        在 ep_poll_callback 中,会先查到对应的 epitem 对象及 eventpoll 对象,将 epitem 放入就绪队列,并检查是否有进程在等待,有就执行唤醒流程。ep_poll_safewake 内部经过逐层调用,没什么营养,最终是调到了 default_wake_function :

// pos: kernel/sched.c
int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,
			  void *key)
{
    // curr 为等待队列上被阻塞的进程,这里将会唤醒之
	return try_to_wake_up(curr->private, mode, wake_flags);
}

5.3 唤醒任务

        上一小节看到之前阻塞在 epoll_wait 上的进程被执行唤醒流程,那么进程睡醒后,就是从上次中断的地方开始继续运行(此时是在进程上下文的内核态):

static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
		   int maxevents, long timeout)
{
    // 前面是在这里 schedule 出去了			
	jtimeout = schedule_timeout(jtimeout);
    
    // 进程睡醒就会走到这里了
    // 检查是否有事件就绪
	eavail = !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR;

	// 如果已经有就绪的事件,就将事件传输到用户空间
	if (!res && eavail &&
	    !(res = ep_send_events(ep, events, maxevents)) && jtimeout)
		goto retry;

	return res;
}

        所以这里事件就绪后,继续执行原有的流程,将就绪事件拷贝到用户空间,随后 epoll_wait 系统调用返回,将从内核态切换到用户态了。

6 总结

        完整的总结一下前面学到的流程:

  1. 用户态调用 epoll_create
  2. 内核态创建 eventpoll 对象,维护:红黑树、就绪队列、等待队列
  3. 用户态调用 epoll_ctl 执行 ADD
  4. 内核态将 ep_poll_callback 回调设置到 socket 的等待队列上,并将 epitem 节点插入红黑树;检查当前是否有事件就绪,有就返回,没有就让出 CPU
  5. 数据包到来,网卡发出硬中断,中断处理函数中再触发软中断。软中断中执行协议栈收包流程:skb 被放入 socket 等待队列,随后开始遍历执行等待队列上的回调函数,执行到了 ep_poll_callback
  6. 在 ep_poll_callback 回调中:epitem 被放入就绪队列;检查等待队列,如果有任务在等待,唤醒之
  7. 任务被唤醒,处于进程上下文的内核态中。从原来被阻塞的地方开始继续执行:把就绪的事件拷贝到用户态
  8. 系统调用返回,处于进程上下文的用户态了

        所以为什么说 epoll 高效呢,不只有通过红黑树高效管理监听的事件,而且有通过 socket 回调机制快速找到文件对应的 epitem,并且只返回就绪事件给用户,避免用户遍历全量的文件描述符。

        另外,如果在监听事件非常多、就绪事件非常多的情况下,就绪事件从内核态到用户态拷贝开销也就不可忽视了,应该可以考虑用共享内存来避免这一次拷贝

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第 126 场 LeetCode 双周赛题解

A 求出加密整数的和 模拟 class Solution { public:int sumOfEncryptedInt(vector<int> &nums) {int res 0;for (auto x: nums) {string s to_string(x);char ch *max_element(s.begin(), s.end());for (auto &c: s)c ch;res stoi(s);}return res;} };B 执行…

Java学习笔记(15)

JDK7前时间相关类 Date时间类 Simpledateformat Format 格式化 Parse 解析 默认格式 指定格式 EE&#xff1a;表示周几 Parse&#xff1a;把字符串时间转成date对象 注意&#xff1a;创建对象的格式要和字符串的格式一样 Calendar日历类 不能创建对象 Getinstance 获取当…

8款手机宝藏APP,每款都非常强大实用!

1. 综合AI工具箱——HuluAI 综合AI工具https://h5.cxyhub.com/?invitationhmeEo7 HuluAI是一款聚合式全能AI工具&#xff0c;完美接入官方正版GPT4.0和Midjourney绘画&#xff01;。除此之外&#xff0c;还拥有文心一言语言大模型和DallE3绘图功能。经过长时间的稳定运行&am…

【数据结构】深入理解AVL树:实现和应用

AVL树是一种自平衡的二叉搜索树&#xff0c;它能够保持良好的平衡性质&#xff0c;使得在最坏情况下的时间复杂度也能保持在对数级别。本文将深入介绍AVL树的原理、实现和应用&#xff0c;并通过示例代码演示其基本操作。 文章目录 什么是AVL树&#xff1f;AVL树的实现在AVL树…

Linux - 安装 Jenkins(详细教程)

目录 前言一、简介二、安装前准备三、下载与安装四、配置镜像地址五、启动与关闭六、常用插件的安装 前言 虽然说网上有很多关于 Jenkins 安装的教程&#xff0c;但是大部分都不够详细&#xff0c;或者是需要搭配 docker 或者 k8s 等进行安装&#xff0c;对于新手小白而已&…

2024人工智能四大趋势→

2023年&#xff0c;世人见证了ChatGPT在全球范围的大火。以生成式人工智能为代表的新一代人工智能问世&#xff0c;改变了人工智能&#xff08;AI&#xff09;技术与应用的发展轨迹&#xff0c;加速了人与AI的互动进程&#xff0c;是人工智能发展史上的新里程碑。2024年&#x…

职场中的“跨界思维”:如何拓宽你的职业发展道路?

在当今职场&#xff0c;单一技能的竞争已经越来越激烈&#xff0c;具备跨界思维的人才越来越受到企业的青睐。本文将探讨职场中的“跨界思维”&#xff0c;帮助您拓宽职业发展道路&#xff0c;提升自身竞争力。 一、什么是跨界思维&#xff1f; 跨界思维&#xff0c;顾名思义&a…

【重新定义matlab强大系列十八】Matlab深度学习长短期记忆 (LSTM) 网络生成文本

&#x1f517; 运行环境&#xff1a;Matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

Etcd 介绍与使用(入门篇)

etcd 介绍 etcd 简介 etc &#xff08;基于 Go 语言实现&#xff0c;&#xff09;在 Linux 系统中是配置文件目录名&#xff1b;etcd 就是配置服务&#xff1b; etcd 诞生于 CoreOS 公司&#xff0c;最初用于解决集群管理系统中 os 升级时的分布式并发控制、配置文件的存储与分…

Bean的作用域、Bean的自动装配、注解自动装配 (Spring学习笔记五)

1、Bean 的作用域 官网上显示有六种 1、Bean的作用域默认的是singleton&#xff08;单例模式的实现&#xff09; 也可以显示的设置&#xff08;单例模式的实现&#xff09; <!--用scope可以设置Bean的作用域--><bean id"user2" class"com.li.pojo.Us…

Elasticsearch从入门到精通-05ES匹配查询

Elasticsearch从入门到精通-05ES匹配查询 &#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是程序员行走的鱼 &#x1f4d6; 本篇主要介绍和大家一块学习一下ES各种场景下的匹配查询,有助于我们在项目中进行综合使用 前提 创建索引并指定ik分词器: PUT /es_db {"…

[ Linux ] vim的使用(附:命令模式的常见命令列表)

1.下载安装 这里是在通过yum进行下载安装 yum install -y vim 2.了解 vim是一款编辑器&#xff0c;它具有多模式的特点 主要有&#xff1a;插入模式&#xff0c;命令模式&#xff0c;底行模式 3.使用 打开 vim 文件名 命令模式的常见命令列表 插入模式 按「 i 」切换…

建设IAM/IDM统一身份管理,实现系统之间的单点登录(SSO)

企业实施身份管理的现状&#xff1a; 1.身份存储分散&#xff0c;不能统一供应诸多应用系统&#xff0c;企业用户信息常常存在于多个系统&#xff0c;如HR系统有一套用户信息&#xff0c;OA系统也有一套用户信息&#xff0c;身份存储不集中&#xff0c;不能统一地为诸多应用系…

BUUCTF-WEB1

[ACTF2020 新生赛]Exec1 1.打开靶机 是一个ping命令 2.利用管道符“|” ping一下本地主机并查看ls ping 127.0.0.1 | ls 可以看到回显的内容是一个文件 127.0.0.1 | cat index.php #查看主机下index.php 127.0.0.1 | ls / #查看主机根目录下的文件 看的一个flag文件 …

C++:菱形继承与虚继承

看下面这个示例代码 class A{ public: int num10; A(){cout<<"A构造"<<endl;} virtual void fun(){cout<<"A虚函数"<<endl;} };class B:public A{ public: B(){cout<<"B构造"<<endl;} void fun(){cout<…

Linux基本使用

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;热门专栏&#xff1a;网络原理&#x1f4d5;格言&#xff1a;那些在暗处执拗生长的花&#xff0c;终有一日会馥郁传香欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 Linux是什么&#xff1f; Linux常用命令介绍 命令提示…

云服务器容器常用操作系统介绍

常用操作系统介绍 开源软件国内镜像源Alpine操作系统介绍镜像源修改镜像源apk包管理器 Debian操作系统介绍镜像源修改镜像源apt包管理器 ubuntu操作系统介绍修改镜像源apt包管理器 CentOS操作系统介绍修改镜像源yum包管理器 开源软件国内镜像源 名称地址南京大学mirror.nju.ed…

【安全类书籍-2】Web渗透测试:使用Kali Linux

目录 内容简介 作用 下载地址 内容简介 书籍的主要内容是指导读者如何运用Kali Linux这一专业的渗透测试平台对Web应用程序进行全面的安全测试。作者们从攻击者的视角出发,详细阐述了渗透测试的基本概念和技术,以及如何配置Kali Linux以适应渗透测试需求。书中不仅教授读者…

初识Java篇(JavaSE基础语法)(1)

个人主页&#xff08;找往期文章包括但不限于本期文章中不懂的知识点&#xff09;&#xff1a; 我要学编程(ಥ_ಥ)-CSDN博客 目录 前言&#xff1a; 初识Java 运行Java程序 注释 标识符 关键字 数据类型与变量 字面常量 数据类型 变量 类型转换 类型提升 字…

Android Kotlin(五)数据流StateFlow和LiveData

Android 上的 Kotlin 数据流 在协程中&#xff0c;与仅返回单个值的挂起函数相反&#xff0c;数据流可按顺序发出多个值。数据流以协程为基础构建&#xff0c;可提供多个值。从概念上来讲&#xff0c;数据流是可通过异步方式进行计算处理的一组数据序列。所发出值的类型必须…
最新文章