epoll

epoll为什么这么快?当数据包到达时,socket是怎么通知epoll的?
(PS:既然要看内核,那就只关心想知道的内容,否则可能会把自己绕晕了!)

先看怎么注册监听句柄的:

long sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event)
{
	struct file *file, *tfile;
	struct eventpoll *ep;

            // 从user space拷到kernel space
        if (ep_op_has_event(op) && copy_from_user(&epds, event, sizeof(struct epoll_event)))
	        goto error_return;

	file = fget(epfd);
	tfile = fget(fd);
	ep = file->private_data;    // 这个file关联epoll实例 

	switch (op) {
	case EPOLL_CTL_ADD:
		epds.events |= POLLERR | POLLHUP;
		error = ep_insert(ep, &epds, tfile, fd);	// 关键看插入操作
		break;
	case EPOLL_CTL_DEL: ...
	case EPOLL_CTL_MOD: ...
	}
	...
}

static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd)
{
	...
	init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);	// 等同于赋值:epq.pt.qproc=ep_ptable_queue_proc
	int revents = tfile->f_op->poll(tfile, &epq.pt);	// 扫一遍就绪事件,再回调ep_ptable_queue_proc挂监听钩子
	ep_rbtree_insert(ep, epi);		// 插入到rbtree里
      ...
      // 检查事件
      if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
          /* 怎么可能会走这里?才刚insert就想dispatch到哪去?真正挂监听的是在epoll_wait里边呀 */
          list_add_tail(&epi->rdllink, &ep->rdllist);
    
          if (waitqueue_active(&ep->wq))	// wq的初始化见ep_alloc(),插入见ep_poll()
              __wake_up_locked(&ep->wq, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE);
          if (waitqueue_active(&ep->poll_wait))
              pwake++;
      }
	...
}

那个tfile->f_op->poll很关键,它是哪来的?这得看sys_socket了,创建待监听的socket的时候初始化的(这里我们只关注socket,不关注普通的文件)。这里略过socket创建时的来龙去脉,它其实指向sock_poll函数。

unsigned int sock_poll(struct file *file, poll_table *wait)
{
	struct socket *sock = file->private_data;
	return sock->ops->poll(file, sock, wait);	// 关键是ops
}

// 传输层的socket结构
struct socket {
	const struct proto_ops	*ops;	// 就是这个
	struct file		*file;
	struct sock		*sk;
	...
}

现在来关注ops是啥,下面是常见的两种socket的ops:

struct inet_protosw inetsw_array[] =
{
	{
		.type =       SOCK_STREAM,
		.protocol =   IPPROTO_TCP,
		.prot =       &tcp_prot,
		.ops =        &inet_stream_ops,	// TCP关注这里
		.capability = -1,
		.no_check =   0,
		.flags =      INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK,
	},
	{
		.type =       SOCK_DGRAM,
		.protocol =   IPPROTO_UDP,
		.prot =       &udp_prot,
		.ops =        &inet_dgram_ops,	// UDP关注这里
		.capability = -1,
		.no_check =   UDP_CSUM_DEFAULT,
		.flags =      INET_PROTOSW_PERMANENT,
	},
	...
};

拿udp举例:

struct proto_ops inet_dgram_ops = {
	.family		   = PF_INET,
	.owner		   = THIS_MODULE,
	.release	   = inet_release,
	.bind		   = inet_bind,
	.connect	   = inet_dgram_connect,
	.socketpair	   = sock_no_socketpair,
	.accept		   = sock_no_accept,
	.getname	   = inet_getname,
	.poll		   = udp_poll,		// 关键
	.ioctl		   = inet_ioctl,
	.listen		   = sock_no_listen,
	.shutdown	   = inet_shutdown,
	.setsockopt	   = sock_common_setsockopt,
	.getsockopt	   = sock_common_getsockopt,
	.sendmsg	   = inet_sendmsg,
	.recvmsg	   = sock_common_recvmsg,
	.mmap		   = sock_no_mmap,
	.sendpage	   = inet_sendpage,
	.compat_setsockopt = compat_sock_common_setsockopt,
	.compat_getsockopt = compat_sock_common_getsockopt,
};

unsigned int udp_poll(struct file *file, struct socket *sock, poll_table *wait)
{
	unsigned int mask = datagram_poll(file, sock, wait);	// 关键
	...
}

// 上面提到的sock->ops->poll就是这个函数
// file是关联待监听fd的,sock是待监听socket的,wait是新建的关联ep_ptable_queue_proc
unsigned int datagram_poll(struct file *file, struct socket *sock, poll_table *wait)
{
	struct sock *sk = sock->sk;
	unsigned int mask;

	poll_wait(file, sk->sk_sleep, wait);	// 关键
	mask = 0;

	/* exceptional events? */
	if (sk->sk_err || !skb_queue_empty(&sk->sk_error_queue))
		mask |= POLLERR;
	if (sk->sk_shutdown & RCV_SHUTDOWN)
		mask |= POLLRDHUP;
	if (sk->sk_shutdown == SHUTDOWN_MASK)
		mask |= POLLHUP;

	/* readable? */
	if (!skb_queue_empty(&sk->sk_receive_queue) ||
		(sk->sk_shutdown & RCV_SHUTDOWN))
		mask |= POLLIN | POLLRDNORM;

	/* Connection-based need to check for termination and startup */
	if (connection_based(sk)) {
		if (sk->sk_state == TCP_CLOSE)
			mask |= POLLHUP;
		/* connection hasn't started yet? */
		if (sk->sk_state == TCP_SYN_SENT)
			return mask;
	}

	/* writable? */
	if (sock_writeable(sk))
		mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
	else
		set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);

	return mask;
}

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
	if (p && wait_address)
		p->qproc(filp, wait_address, p);	// qproc其实就是 ep_ptable_queue_proc
}

// file是关联待监听fd
// whead是待监听网络层sk->sk_sleep
// pt关联ep_ptable_queue_proc的结构体
void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt)
{
	struct epitem *epi = ep_item_from_epqueue(pt);  // epi在ep_insert出现过,表示一个待监听的fd
	struct eppoll_entry *pwq;   // 这个玩意准备挂在epi上面

	if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
		init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);	// ep_poll_callback是回调
		pwq->whead = whead;
		pwq->base = epi;
		add_wait_queue(whead, &pwq->wait);	// whead插到pwq->wait链表中
		list_add_tail(&pwq->llink, &epi->pwqlist);	// pwq->llink插到epi->pwqlist链表中
		epi->nwait++;
	} else {
		/* We have to signal that an error occurred */
		epi->nwait = -1;
	}
}

等待事件发生

long sys_epoll_wait(int epfd, struct epoll_event __user *events,
			       int maxevents, int timeout)
{
	...
	error = ep_poll(ep, events, maxevents, timeout);
	return error;
}


int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, int maxevents, long timeout)
{
	int res, eavail;
	unsigned long flags;
	long jtimeout;
	wait_queue_t wait;

	jtimeout = (timeout < 0 || timeout >= EP_MAX_MSTIMEO) ?
		MAX_SCHEDULE_TIMEOUT : (timeout * HZ + 999) / 1000;

retry:
	spin_lock_irqsave(&ep->lock, flags);

	res = 0;
	if (list_empty(&ep->rdllist)) {
		init_waitqueue_entry(&wait, current);	// 当前进程关联wait
		wait.flags |= WQ_FLAG_EXCLUSIVE;
		__add_wait_queue(&ep->wq, &wait);	// 加入ep的等待队列,有事件发生就会notify本进程

		for (;;) {	// 运行期间无限循环
			set_current_state(TASK_INTERRUPTIBLE);	// 让当前进程随时可被打断
			if (!list_empty(&ep->rdllist) || !jtimeout)
				break;
			if (signal_pending(current)) {
				res = -EINTR;
				break;
			}

			spin_unlock_irqrestore(&ep->lock, flags);
			jtimeout = schedule_timeout(jtimeout); // 调度,睡眠
			spin_lock_irqsave(&ep->lock, flags);
		}
		__remove_wait_queue(&ep->wq, &wait);  // 移除等待队列

		set_current_state(TASK_RUNNING);	// 置本进程状态为running
	}


	eavail = !list_empty(&ep->rdllist);
	spin_unlock_irqrestore(&ep->lock, flags);
	if (!res && eavail && !(res = ep_send_events(ep, events, maxevents)) && jtimeout)
		goto retry;
	return res;
}

由于设置了TASK_INTERRUPTIBLE状态,schedule_timeout(jtimeout)可能还没睡够jtimeout就返回,比如接收到信号。等到返回时,状态自动被切换到TASK_RUNNING。

创建socket流程

socket是怎么创建的?

// 系统调用入口
long sys_socket(int family, int type, int protocol)
{
	...
	retval = sock_create(family, type, protocol, &sock);
	return sock_map_fd(sock);	// 将socket映射成fd
}

int sock_create(int family, int type, int protocol, struct socket **res)
{
	return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}

// 系统调用sys_socket是这样创建
int __sock_create(struct net *net, int family, int type, int protocol, struct socket **res, int kern)
{
	...
	struct socket *sock = sock_alloc();	// 这是传输层的!
	pf = rcu_dereference(net_families[family]);	// net_families是全局变量,供其他模块注册
	err = pf->create(net, sock, protocol);	// 创建对应IP层的sock,见下面分析
	...
}

对于AF_INET,pf就是下面这样来的。

// families注册接口
int sock_register(const struct net_proto_family *ops)
{
	if (net_families[ops->family])
		err = -EEXIST;
	else {
		net_families[ops->family] = ops;
		err = 0;
	}
}

int inet_init(void)
{
	...
	sock_register(&inet_family_ops);	// 注册
	...
}

struct net_proto_family inet_family_ops = {
	.family = PF_INET,
	.create = inet_create,		// 就是那个pf->create
	.owner	= THIS_MODULE,
};

IP层的socket才是关键,底层数据包到达终端时是先到达IP层的:

// Create an inet socket.(这是IP层的socket)
int inet_create(struct net *net, struct socket *sock, int protocol)
{
	...
	struct sock *sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);	// 申请sk
	sock_init_data(sock, sk);	// 初始化sk和sock之间关联部分
	...
}

void sock_init_data(struct socket *sock, struct sock *sk)
{
	...
	if (sock) {
		sk->sk_type = sock->type;
		sk->sk_sleep = &sock->wait;
		sock->sk = sk;	// sk挂在sock上
	} else
		sk->sk_sleep = NULL;
	...
}

将socket映射成文件:有些重要的东西是放在文件里的,须要关注下。

int sock_map_fd(struct socket *sock)
{
	struct file *newfile;
	int fd = sock_alloc_fd(&newfile);
	int err = sock_attach_fd(sock, newfile);	// 关键
	fd_install(fd, newfile);
	...
}

static int sock_attach_fd(struct socket *sock, struct file *file)
{
	...
	sock->file = file;
	init_file(file, sock_mnt, dentry, FMODE_READ | FMODE_WRITE, &socket_file_ops);	// 关键
	file->private_data = sock;
	...
}

int init_file(struct file *file, struct vfsmount *mnt, struct dentry *dentry,
   mode_t mode, const struct file_operations *fop)
{
	int error = 0;
	file->f_path.dentry = dentry;
	file->f_path.mnt = mntget(mnt);
	file->f_mapping = dentry->d_inode->i_mapping;
	file->f_mode = mode;
	file->f_op = fop;		// 关键
	return error;
}

const struct file_operations socket_file_ops = {
	.owner =	THIS_MODULE,
	.llseek =	no_llseek,
	.aio_read =	sock_aio_read,
	.aio_write =	sock_aio_write,
	.poll =		sock_poll,			// 关键
	.unlocked_ioctl = sock_ioctl,
	.compat_ioctl = compat_sock_ioctl,
	.mmap =		sock_mmap,
	.open =		sock_no_open,
	.release =	sock_close,
	.fasync =	sock_fasync,
	.sendpage =	sock_sendpage,
	.splice_write = generic_splice_sendpage,
};

怎么通知阻塞的进程的?

posted @ 2018-12-05 21:29  xcw0754  阅读(212)  评论(0编辑  收藏  举报