这里讨论的是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()
的时侯, 我先要去写另一个函数. 因为这是create调用的初始化的函数.
ep_alloc() : 初始化结构
get_cunrent_user
获得用户信息kzalloc()
分配空间初始化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
结构的通道.