一、epoll实现原理及回调机制
epoll是对select系统调用的改善,适用于同时关注文件比较多的时候的一个中优化方法,例如一个具有大量连接的系统。那么select的缺点在哪里呢?当select被唤醒的时候,它并不知道是被哪个或者哪些文件唤醒的,所以它要对位图中所有的文件进行遍历查询(调用该文件的poll接口)。可以想象,如果select的文件比较多,并且大部分文件都是不活跃的,那么这些select中的大部分poll将会没有任何意义。
所以epoll就使用一个相对智能的回调机制:当某个文件准备好之后要唤醒等待线程时,它不是简单的把等待者设置为可运行,它还会进一步在等待者的结构中刻上“XXX到此一游”,这样,当线程被唤醒之后,它就可以通过这些留言看到是谁唤醒了自己,而不是逐个询问刚才是谁把我唤醒了。
这里就体现了软件中比较常用的“回调机制”(callback),当然有些比较通俗的叫法就叫做“钩子”(hook),也就是在某些事件发生的时候知会一些实体,知会的方法就是调用对方提供的钩子函数。因为可能某一个实体对一个事件的发生很感兴趣,但是这个事件并不是随时随地都会发生的,而具体在什么时候发生只有"体制内"流程才知道。比方说你去一个地方找一个同事A,但是他不在,你可能会给他附近的同事B说:“如果他回来的话你告诉我一声”。这就是一个回调机制,当一个自己不知道的事情发生的时候通过一种机制马上通知到自己。
二、epoll与select唤醒方法对比
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
poll_table *pt)
……
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
……
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
list_add_tail(&epi->rdllink, &ep->rdllist);
}
对于epoll系统调用,当poll唤醒等待者时,epoll顺势记录了唤醒者的信息到红黑树。
对于select,它的唤醒函数为
__pollwait--->>>q->func = default_wake_function;
int default_wake_function(wait_queue_t *curr, unsigned mode, int sync,
void *key)
{
return try_to_wake_up(curr->private, mode, sync);
}
对于select调用来说,它只是简单的唤醒,很傻很天真,也就是头脑相对比较简单一些,这个简单的代价就是在do_select函数中有一个罕见的三重for循环。
三、红黑树
红黑树是内核lib文件夹下的一个实现,也就是一个库中提供的数据结构,当前在内核的内存区间管理(mm_struct.mm_rb),公平调度其(task_struct.sched_entity.cfs_rq.rb_root)中均有使用,当然在这里也有使用。它的特点就是保持了较好的平衡性,所以查找和删除都不会太慢。它是AVL树的一个折中,没有AVL树高度限制那么严格,但是操作速度会有相对提高。
四、file结构中的private_data
这个是Linux下连接VFS文件系统框架和不同文件/文件系统底层实现之间的一个核心数据结构,虽然它只是一个指针,但是一个指针可以解决所有问题,有了它,妈妈再也不用担心我的学习。我们回想一下用户态线程的创建结构,函数的入口同样是一个void*指针,而千言万语汇成一根指针,诗可以兴、可以观、可以群、可以怨,可以解决所有问题。
因为file是VFS框架的一个基本概念,它要支持文件操作结构,例如open/read/write/release之类的接口,甚至还有poll等,只有有了这些结构,它们才能被纳入VFS这个大家庭。但是对于不同的设备文件来说,它们只是披着文件外衣的设备,所以他要有自己特有的结构来和设备交流,而这private_data就是这个连接的纽带。这样说可能还是比较抽象,最后是多看一些代码感受可能会深一些。
下面是之前我遇到过的一些使用private_data的一些文件:
1、tty设备
static ssize_t tty_read(struct file * file, char __user * buf, size_t count,
loff_t *ppos)
{
int i;
struct tty_struct * tty;
struct inode *inode;
struct tty_ldisc *ld;
tty = (struct tty_struct *)file->private_data;
2、tun/tap设备
static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv,
unsigned long count, loff_t pos)
{
struct file *file = iocb->ki_filp;
struct tun_struct *tun = file->private_data;
3、套接口文件
static ssize_t do_sock_read(struct msghdr *msg, struct kiocb *iocb,
struct file *file, const struct iovec *iov,
unsigned long nr_segs)
{
struct socket *sock = file->private_data;
size_t size = 0;
4、epoll文件
static int ep_eventpoll_close(struct inode *inode, struct file *file)
{
struct eventpoll *ep = file->private_data;
5、shm文件
long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr)
{
……
file->private_data = sfd;
epoll是对select系统调用的改善,适用于同时关注文件比较多的时候的一个中优化方法,例如一个具有大量连接的系统。那么select的缺点在哪里呢?当select被唤醒的时候,它并不知道是被哪个或者哪些文件唤醒的,所以它要对位图中所有的文件进行遍历查询(调用该文件的poll接口)。可以想象,如果select的文件比较多,并且大部分文件都是不活跃的,那么这些select中的大部分poll将会没有任何意义。
所以epoll就使用一个相对智能的回调机制:当某个文件准备好之后要唤醒等待线程时,它不是简单的把等待者设置为可运行,它还会进一步在等待者的结构中刻上“XXX到此一游”,这样,当线程被唤醒之后,它就可以通过这些留言看到是谁唤醒了自己,而不是逐个询问刚才是谁把我唤醒了。
这里就体现了软件中比较常用的“回调机制”(callback),当然有些比较通俗的叫法就叫做“钩子”(hook),也就是在某些事件发生的时候知会一些实体,知会的方法就是调用对方提供的钩子函数。因为可能某一个实体对一个事件的发生很感兴趣,但是这个事件并不是随时随地都会发生的,而具体在什么时候发生只有"体制内"流程才知道。比方说你去一个地方找一个同事A,但是他不在,你可能会给他附近的同事B说:“如果他回来的话你告诉我一声”。这就是一个回调机制,当一个自己不知道的事情发生的时候通过一种机制马上通知到自己。
二、epoll与select唤醒方法对比
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
poll_table *pt)
……
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
……
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
list_add_tail(&epi->rdllink, &ep->rdllist);
}
对于epoll系统调用,当poll唤醒等待者时,epoll顺势记录了唤醒者的信息到红黑树。
对于select,它的唤醒函数为
__pollwait--->>>q->func = default_wake_function;
int default_wake_function(wait_queue_t *curr, unsigned mode, int sync,
void *key)
{
return try_to_wake_up(curr->private, mode, sync);
}
对于select调用来说,它只是简单的唤醒,很傻很天真,也就是头脑相对比较简单一些,这个简单的代价就是在do_select函数中有一个罕见的三重for循环。
三、红黑树
红黑树是内核lib文件夹下的一个实现,也就是一个库中提供的数据结构,当前在内核的内存区间管理(mm_struct.mm_rb),公平调度其(task_struct.sched_entity.cfs_rq.rb_root)中均有使用,当然在这里也有使用。它的特点就是保持了较好的平衡性,所以查找和删除都不会太慢。它是AVL树的一个折中,没有AVL树高度限制那么严格,但是操作速度会有相对提高。
四、file结构中的private_data
这个是Linux下连接VFS文件系统框架和不同文件/文件系统底层实现之间的一个核心数据结构,虽然它只是一个指针,但是一个指针可以解决所有问题,有了它,妈妈再也不用担心我的学习。我们回想一下用户态线程的创建结构,函数的入口同样是一个void*指针,而千言万语汇成一根指针,诗可以兴、可以观、可以群、可以怨,可以解决所有问题。
因为file是VFS框架的一个基本概念,它要支持文件操作结构,例如open/read/write/release之类的接口,甚至还有poll等,只有有了这些结构,它们才能被纳入VFS这个大家庭。但是对于不同的设备文件来说,它们只是披着文件外衣的设备,所以他要有自己特有的结构来和设备交流,而这private_data就是这个连接的纽带。这样说可能还是比较抽象,最后是多看一些代码感受可能会深一些。
下面是之前我遇到过的一些使用private_data的一些文件:
1、tty设备
static ssize_t tty_read(struct file * file, char __user * buf, size_t count,
loff_t *ppos)
{
int i;
struct tty_struct * tty;
struct inode *inode;
struct tty_ldisc *ld;
tty = (struct tty_struct *)file->private_data;
2、tun/tap设备
static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv,
unsigned long count, loff_t pos)
{
struct file *file = iocb->ki_filp;
struct tun_struct *tun = file->private_data;
3、套接口文件
static ssize_t do_sock_read(struct msghdr *msg, struct kiocb *iocb,
struct file *file, const struct iovec *iov,
unsigned long nr_segs)
{
struct socket *sock = file->private_data;
size_t size = 0;
4、epoll文件
static int ep_eventpoll_close(struct inode *inode, struct file *file)
{
struct eventpoll *ep = file->private_data;
5、shm文件
long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr)
{
……
file->private_data = sfd;