epoll原始碼分析(一)
阿新 • • 發佈:2018-12-04
epoll原始碼分析(一)
文章目錄
主要資料結構
這裡討論的是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() : 初始化結構
- 通過
get_cunrent_user
獲得使用者資訊 - 通過
kzalloc()
分配空間 - 失敗, 釋放獲得的使用者資訊並退出
- 初始化自旋鎖
- 加鎖
- 初始化等待佇列, 就緒佇列, 紅黑樹根
- 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
結構的通道.