epoll源码分析(一)

epoll源码分析(一)

文章目录

  • epoll源码分析(一)
    • @[toc]
    • 主要数据结构
    • epoll_create()函数实现
      • 总结

主要数据结构

这里讨论的是linux2.6的eventpoll.c文件里面的代码, 所以讲的代码基本都是这个文件的, 其他个别不在里面的代码我已经写出了路径.

这里写的代码后面的描述会用到的数据结构和函数, 这里可以先跳过函数, 只了解一下重要结构体的元素就行, 后面讲解的时侯再回头看.

struct eventpoll
{
    spinlock_t lock;	// 自旋锁
    struct mutex mtx;	// 防止使用时被删除
    wait_queue_head_t wq;	// sys_epoll_wait() 使用的等待队列
    wait_queue_head_t poll_wait;	// file->poll() 使用的等待队列
    struct list_head rdllist;	// 事件满足条件的链表, 事件就绪, 准备在epoll_wait时写入用户空间
    struct rb_root rbr;	
    struct epitem *ovflist;	// 将事件到达的fd进行连接, 并发送至用户空间
    struct user_struct *user;
};
struct epitem
{
	struct rb_node rbn;
    struct list_head rdllink;	// 事件的就绪队列
    struct epoll_filed ffd;
    int nwait;
    struct list_ead pwqlist;	// poll 等待队列
    struct eventpoll *ep;		// 属于哪个结构体
    struct list_head fllink;	// 链接fd对应的file链表
    struct epoll_event event;	// 事件
};
struct epoll_filefd
{
    struct file *file;
    int fd;
};
static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next)
{
    next->prev = new;
    new->prev = prev;
    new->next = next;
    prev->next = new;
}

static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}
// 检查是否为空链表
static inline int waitqueue_active(wait_queue_t *q)
{
    return !list_empty(&q->task_list);
}

epoll_create()函数实现

在讨论epoll_create()的时侯, 我先要去写另一个函数. 因为这是create调用的初始化的函数.

ep_alloc() : 初始化结构

  1. 通过get_cunrent_user获得用户信息
  2. 通过kzalloc()分配空间
  3. 失败, 释放获得的用户信息并退出
  4. 初始化自旋锁
  5. 加锁
  6. 初始化等待队列, 就绪队列, 红黑树根
  7. ep 保存用户信息, 红黑树头, 等待队列和就绪队列

初始化eventpoll

// fs/eventpoll.c
static int ep_alloc(strut eventpoll **pep)
{
    ...
    struct user_struct *user;
    struct eventpoll *ep;
    user = get_current_user();	// 获得用户的信息
    ep = kzalloc(sizeog(*ep), GFP_KERNEL);	// 申请空间, kzalloc()与kalloc基本一样, 只是会在申请空间之后将其清零, 避免以前的数据影响.
    /*加锁*/
    spin_lock_init(&ep->lock);
    mutex_init(&ep->mtx);
    
    /*队列的初始化*/
    init_waitqueue_head(&ep->wq);
    init_waitqueue_head(&ep->poll_wait);
    INIT_LIST_HEAED(&ep->rdllist);
    ...
}
// kernel/wait.c
void init_waitqueue_head(wwait_queue_head_t *q)
{
    spin_lock_init(&q->lock);
    INIT_LIST_HEAD(&q->task_list);
}

关于epoll_create()的系统调用, 关于系统调用的SYSCALL_DEFINEx类的函数, 我已经在另一篇博客写好了, 感兴趣的可以去了解一下. 跳转

SYSCALL_DEFINE1(epoll_create1, int, flags)
{
    int errno, fd = -1;
    struct eventpoll *ep;
    ...
    errnno = ep_alloc(&ep);
    ...
    // 重点 : 创建匿名文件, 并返回文件描述符
    fd = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep, flags & O_CLOEXEC);
}
SYSCALL_DEFINE1(epoll_create, int, size)
{
    if(size <= 0)
        return EINVAL;
    // 这里我一直认为是size, 但是源码是这样, 并且也没有人改, 应该是我没有理解吧.
    return sys_epoll_create1(0);
}

好了, epoll_create重要的分配空间已经讲了, 剩下还有一个重点是创建匿名文件, 返回匿名文件的文件描述符.

// 创建文件描述符, inode 指针的指向
int anon_inode_getfd(const char *name, const struct file_operations *fops,
		     void *priv, int flags)
{
	struct qstr this;
	struct dentry *dentry;
	struct file *file;
	int error, fd;

    // 完成文件描述符的分配
	error = get_unused_fd_flags(flags);
	if (error < 0)
		return error;
	fd = error;
    
	error = -ENOMEM;
    // 初始化即将放入目录项中的文件名
	this.name = name;
	this.len = strlen(name);
	this.hash = 0;
    // 文件名信息加入到目录项中
	dentry = d_alloc(anon_inode_mnt->mnt_sb->s_root, &this);
	if (!dentry)
		goto err_put_unused_fd;
	// 为文件分配空间, 映射地址
	...

	return fd;
err_dput:
	...
}

在我去查get_unused_fd_flags函数的时侯, 发现它是这样的, 之前我就很不明白这样的好处, 现在感觉就是给相同的调用取一个符合的别名, 容易理解.

// include/linux/file.h
#define get_used_fd_flags(flags) alloc_fd(0, (flags))

以下就是文件描述符的分配

// 创建文件, 文件, 返回文件描述符
// 这里传入的 start的值为 0
int alloc_fd(unsigned start, unsigned flags)
{
    // currnet指向当前进程的指针, 通过current获得进程的文件表
	struct files_struct *files = current->files;
	unsigned int fd;
	int error;
	struct fdtable *fdt;

	spin_lock(&files->file_lock);
repeat:
    // 通过进程的打开文件列表获得文件描述符位图结构, 说白了就是打开进程的文件描述符表
	fdt = files_fdtable(files);
	fd = start;
	if (fd < files->next_fd)
		fd = files->next_fd;
	if (fd < fdt->max_fds)
        // 在文件描述符表中找到一个空闲的bit位, 找到空闲的bit位就相当于找到一个文件描述符
		fd = find_next_zero_bit(fdt->open_fds->fds_bits,fdt->max_fds, fd);
    // 通过fd值, 判断是否对文件描述符表进行扩展
	error = expand_files(files, fd);
    // 错误判断
	...
	if (start <= files->next_fd)
		files->next_fd = fd + 1;
    
	FD_SET(fd, fdt->open_fds);
	if (flags & O_CLOEXEC)
		FD_SET(fd, fdt->close_on_exec);
	else
		FD_CLR(fd, fdt->close_on_exec);
	...
}

总结

函数从SYSCALL_DEFINE1(epoll_create)开始, 调用ep_alloc函数对event_poll分配内存空间, 然后再初始化红黑树, 就绪队列链表等, 最重要的是alloc_fd函数的调用, 为epoll创建一个存放数据的匿名文件, 并为文件设置好inode索引号, 文件名存放在目录项中.最后, 返回文件打开的文件描述符, 将文件的信息放入进程的task_struct结构中的文件描述符表中.

epoll_create就是进程在内核中创建了一个从epoll文件描述符到eventpoll结构的通道.

你可能感兴趣的:(UNIX高级编程随笔,linux深入理解,unix编程学习)