1. 程式人生 > >epoll原始碼分析(一)

epoll原始碼分析(一)

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() : 初始化結構

  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結構的通道.