unix域源码解析 - Go语言中文社区

unix域源码解析


在这里插入图片描述
首先我们先要创建一个用于通信的结构unix_proto_data ,并初始化某些字段

static int unix_proto_create(struct socket *sock, int protocol)
{
	struct unix_proto_data *upd;

	/*
	 *	No funny SOCK_RAW stuff
	 */
	 
	if (protocol != 0) 
	{
		return(-EINVAL);
	}
	// 分配一个unix_proto_data结构体
	if (!(upd = unix_data_alloc())) 
	{
		printk("UNIX: create: can't allocate buffern");
		return(-ENOMEM);
	}
	// 给unix_proto_data的buf字段分配一个页大小的内存
	if (!(upd->buf = (char*) get_free_page(GFP_USER))) 
	{
		printk("UNIX: create: can't get page!n");
		unix_data_deref(upd);
		return(-ENOMEM);
	}
	upd->protocol = protocol;
	// 关联unix_proto_data对应的socket结构
	upd->socket = sock;
	// socket的data字段指向unix_proto_data结构
	UN_DATA(sock) = upd;
	// 标记unix_proto_data已被使用
	upd->refcnt = 1;	/* Now it's complete - bgm */
	return(0);
}

接着给这个结构绑定"地址信息",一个文件路径。bind函数主要是根据传进来的路径创建一个文件,如果已经存在则报错。否则新建成功后把inode节点,路径名等信息存在unix_proto_data 结构

// 把sockaddr的内容存在unix_proto_data中,并创建一个文件
static int unix_proto_bind(struct socket *sock, struct sockaddr *umyaddr,
		int sockaddr_len)
{
	char fname[UNIX_PATH_MAX + 1];
	struct unix_proto_data *upd = UN_DATA(sock);
	unsigned long old_fs;
	int i;

	if (sockaddr_len <= UN_PATH_OFFSET ||
		sockaddr_len > sizeof(struct sockaddr_un)) 
	{
		return(-EINVAL);
	}
	if (upd->sockaddr_len || upd->inode) 
	{
		/*printk("UNIX: bind: already bound!n");*/
		return(-EINVAL);
	}
	// sockaddr_un兼容sockaddr结构
	memcpy(&upd->sockaddr_un, umyaddr, sockaddr_len);
	/*
		UN_PATH_OFFSET为sun_path在sockaddr_un结构中的偏移,
		sockaddr_len-UN_PATH_OFFSET等于sun_path的最后一个字符+1的位置
	*/
	upd->sockaddr_un.sun_path[sockaddr_len-UN_PATH_OFFSET] = '';
	if (upd->sockaddr_un.sun_family != AF_UNIX) 
	{
		return(-EINVAL);
	}
	// 把sun_path的值放到fname中
	memcpy(fname, upd->sockaddr_un.sun_path, sockaddr_len-UN_PATH_OFFSET);
	fname[sockaddr_len-UN_PATH_OFFSET] = '';
	old_fs = get_fs();
	set_fs(get_ds());
	// 新建一个inode节点,文件名是fname,标记是一个socket类型
	i = do_mknod(fname, S_IFSOCK | S_IRWXUGO, 0);

	if (i == 0) 
		i = open_namei(fname, 0, S_IFSOCK, &upd->inode, NULL); // &upd->inode保存打开文件对应的inode节点
	set_fs(old_fs);
	if (i < 0) 
	{
/*		printk("UNIX: bind: can't open socket %sn", fname);*/
		if(i==-EEXIST)
			i=-EADDRINUSE;
		return(i);
	}
	upd->sockaddr_len = sockaddr_len;	/* now it's legal */
	
	return(0);
}

有了地址后,我们可以作为服务端或客户端,下面分开说,我们先说作为服务端
由于该版本没有支持listen函数。调用socket.c的listen函数时,unix没有对应的操作。所以我们可以直接调accept。通过代码我们知道调用socket.c的accept函数的时候,首先创建了一个新的socket结构,用于accept返回的时候,然后在该函数里面首先调用了另一个函数dup。该函数主要是创建比socket结构还底层的一个结构,然后和socket结构关联起来。所以我们先看看unix域层的dup函数。

/* 
	创建一个新的unix_proto_data结构和socket关联,newsock由上层新创建的,即首先创建了一个新的socket结构,
	根据oldsock的协议类型,创建了一个新的unix_proto_data和newsock关联
*/
static int unix_proto_dup(struct socket *newsock, struct socket *oldsock)
{
	struct unix_proto_data *upd = UN_DATA(oldsock);
	return(unix_proto_create(newsock, upd->protocol));
}

接着调accept,该函数主要是从socket的连接队列上不断地摘取连接节点,然后唤醒客户端,如果没有连接则阻塞自己,等待有连接的时候被唤醒。unix域中建立连接的本质是客户端和服务端的数据结构互相关联。从而完成通信。


static int unix_proto_accept(struct socket *sock, struct socket *newsock, int flags)
{
	struct socket *clientsock;

/*
 * If there aren't any sockets awaiting connection,
 * then wait for one, unless nonblocking.
 */
	// sock为服务端socket,iconn是连接队列,先判断是否有连接
	while(!(clientsock = sock->iconn)) 
	{	
		// 为空并且设置了非阻塞直接返回
		if (flags & O_NONBLOCK) 
			return(-EAGAIN);
		// 设置等待连接标记
		sock->flags |= SO_WAITDATA;
		// 阻塞,有有人connect的时候被唤醒
		interruptible_sleep_on(sock->wait);
		// 清除等待连接标记
		sock->flags &= ~SO_WAITDATA;
		if (current->signal & ~current->blocked) 
		{
			return(-ERESTARTSYS);
		}
	}
/*
 * Great. Finish the connection relative to server and client,
 * wake up the client and return the new fd to the server.
 */
	// 更新服务端的连接队列,摘下了第一个节点
	sock->iconn = clientsock->next;
	clientsock->next = NULL;
	// 新生成的socket结构,对端指向客户端
	newsock->conn = clientsock;
	// 互相引用,设置状态为已连接
	clientsock->conn = newsock;
	clientsock->state = SS_CONNECTED;
	newsock->state = SS_CONNECTED;
	// unix_proto_data结构的引用数加1
	unix_data_ref(UN_DATA(clientsock));
	// 把unix_proto_data的数据复制到sock结构中,保存客户端的路径信息
	UN_DATA(newsock)->peerupd	     = UN_DATA(clientsock);
	UN_DATA(newsock)->sockaddr_un        = UN_DATA(sock)->sockaddr_un;
	UN_DATA(newsock)->sockaddr_len       = UN_DATA(sock)->sockaddr_len;
	// 唤醒被阻塞的客户端队列
	wake_up_interruptible(clientsock->wait);
	sock_wake_async(clientsock, 0);
	return(0);
}

接下来我们看connect函数,connect函数主要是把自客户端的追加到服务端的连接队列,阻塞自己,等待服务端进行处理,然后被唤醒,期间不断完成数据的互相关联。

	memcpy(fname, sockun.sun_path, sockaddr_len-UN_PATH_OFFSET);
	fname[sockaddr_len-UN_PATH_OFFSET] = '';
	old_fs = get_fs();
	set_fs(get_ds());
	// 根据传入的路径打开该文件,把inode存在inode变量里
	i = open_namei(fname, 2, S_IFSOCK, &inode, NULL);
	set_fs(old_fs);
	if (i < 0) 
	{
		return(i);
	}
	// 从unix_proto_data表中找到服务端对应的unix_proto_data结构  
	serv_upd = unix_data_lookup(&sockun, sockaddr_len, inode);
	iput(inode);
	// 没有则说明服务端不存在
	if (!serv_upd) 
	{
		return(-EINVAL);
	}
	// 把客户端追加到服务端的连接队列,阻塞自己,等待服务器处理后唤醒
	if ((i = sock_awaitconn(sock, serv_upd->socket, flags)) < 0) 
	{
		return(i);
	}
	// conn为服务端socket
	if (sock->conn) 
	{	// 服务端unix_proto_data结构引用数加一,并指向服务端unix_proto_data结构
		unix_data_ref(UN_DATA(sock->conn));
		UN_DATA(sock)->peerupd = UN_DATA(sock->conn); /* ref server */
	}
	return(0);
}
// 把客户端socket追加到服务端的队列结尾,设置客户端的的对端是服务端的socket,唤醒服务端处理请求,当前进程阻塞,等待唤醒	
int sock_awaitconn(struct socket *mysock, struct socket *servsock, int flags)
{
	struct socket *last;

	/*
	 *	We must be listening
	 */
	// 调用listen的时候设置的
	if (!(servsock->flags & SO_ACCEPTCON)) 
	{
		return(-EINVAL);
	}

  	/*
  	 *	Put ourselves on the server's incomplete connection queue. 
  	 */
  	 
	mysock->next = NULL;
	cli();
	// 把客服端socket加到服务端的连接队列
	if (!(last = servsock->iconn)) // 队列为空,则当前客户端为第一个连接节点 
		servsock->iconn = mysock; 
	else 
	{	// 找到队尾,然后追加到队尾
		while (last->next) 
			last = last->next;
		last->next = mysock;
	}
	mysock->state = SS_CONNECTING;
	// 设置客户端的对端
	mysock->conn = servsock;
	sti();

	/*
	 * Wake up server, then await connection. server will set state to
	 * SS_CONNECTED if we're connected.
	 */
	// 有连接到来,唤醒服务端
	wake_up_interruptible(servsock->wait);
	sock_wake_async(servsock, 0);
	
	if (mysock->state != SS_CONNECTED) 
	{	
		// 此时state为SS_CONNECTING,非阻塞则直接返回
		if (flags & O_NONBLOCK)
			return -EINPROGRESS;
		// 否则阻塞当前发起连接的进程,等待服务端处理连接,设置state为SS_CONNECTED,然后唤醒客户端
		interruptible_sleep_on(mysock->wait);
		// 状态不对,删除该客户端
		if (mysock->state != SS_CONNECTED &&
		    mysock->state != SS_DISCONNECTING) 
		{
		/*
		 * if we're not connected we could have been
		 * 1) interrupted, so we need to remove ourselves
		 *    from the server list
		 * 2) rejected (mysock->conn == NULL), and have
		 *    already been removed from the list
		 */
			if (mysock->conn == servsock) 
			{
				cli();
				// 服务端连接队列只有一个节点
				if ((last = servsock->iconn) == mysock)
					servsock->iconn = mysock->next;
				else 
				{	// 找到mysock的前一个节点,删除mysock
					while (last->next != mysock) 
						last = last->next;
					last->next = mysock->next;
				}
				sti();
			}
			return(mysock->conn ? -EINTR : -EACCES);
		}
	}
	return(0);
}

到这里,我们完成了建立连接的过程。接下来我们可以进行全双工的通信了。讲数据通信之前首先要讲一下可回环的缓冲区,他本质是一个一定大小的数组,数据写到最后一个索引后,如果前面的索引对应的元素是空,则可以往回开始写。unix域里主要是一个一页大小的字节数组作为通信的缓冲区。然后他有两个头尾指针,分别代码可写空间的起始索引和结束索引。当一端向另一端写数据的时候,直接写到对端的缓冲区去,然后对端就可以读了。初始化的时候head和tail都是0,可写空间是缓冲区大小,因为head要追上tail需要移动一页大小,当对端往里面写10个字节的时候,head往后移动10位,这时候可写字节数等于一页-10,而本端则通过tail指针可知道从哪里是可读的数据。head-tail知道还有多少空间可写,再和一页进行计算,就知道有多少空间可读,读指针是tail。

 
static int unix_proto_read(struct socket *sock, char *ubuf, int size, int nonblock)
{
	struct unix_proto_data *upd;
	int todo, avail;

	if ((todo = size) <= 0) 
		return(0);

	upd = UN_DATA(sock);
	// 看buf中有多少数据可读
	while(!(avail = UN_BUF_AVAIL(upd))) 
	{
		if (sock->state != SS_CONNECTED) 
		{
			return((sock->state == SS_DISCONNECTING) ? 0 : -EINVAL);
		}
		// 没有数据,但是以非阻塞模式,直接返回
		if (nonblock) 
			return(-EAGAIN);
		// 阻塞等待数据
		sock->flags |= SO_WAITDATA;
		interruptible_sleep_on(sock->wait);
		// 唤醒后清除等待标记位
		sock->flags &= ~SO_WAITDATA;
		if (current->signal & ~current->blocked) 
		{
			return(-ERESTARTSYS);
		}
	}

/*
 *	Copy from the read buffer into the user's buffer,
 *	watching for wraparound. Then we wake up the writer.
 */
    // 加锁
	unix_lock(upd);
	do 
	{
		int part, cando;

		if (avail <= 0) 
		{
			printk("UNIX: read: AVAIL IS NEGATIVE!!!n");
			send_sig(SIGKILL, current, 1);
			return(-EPIPE);
		}
		// 要读的比可读的多,则要读的为可读的数量
		if ((cando = todo) > avail) 
			cando = avail;
		// 有一部分数据在队尾,一部分在队头,则先读队尾的,bp_tail表示可写空间的最后一个字节加1,即可读的第一个字节
		if (cando >(part = BUF_SIZE - upd->bp_tail)) 
			cando = part;
		memcpy_tofs(ubuf, upd->buf + upd->bp_tail, cando);
		// 更新bp_tail,可写空间增加
		upd->bp_tail =(upd->bp_tail + cando) &(BUF_SIZE-1);
		// 更新用户的buf指针
		ubuf += cando;
		// 还需要读的字节数
		todo -= cando;
		if (sock->state == SS_CONNECTED)
		{
			wake_up_interruptible(sock->conn->wait);
			sock_wake_async(sock->conn, 2);
		}
		avail = UN_BUF_AVAIL(upd);
	} 
	while(todo && avail);// 还有数据并且还没读完则继续
	unix_unlock(upd);
	return(size - todo);// 要读的减去读了的
}


/*
 *	We write to our peer's buf. When we connected we ref'd this
 *	peer so we are safe that the buffer remains, even after the
 *	peer has disconnected, which we check other ways.
 */
 
static int unix_proto_write(struct socket *sock, char *ubuf, int size, int nonblock)
{
	struct unix_proto_data *pupd;
	int todo, space;

	if ((todo = size) <= 0)
		return(0);
	if (sock->state != SS_CONNECTED) 
	{
		if (sock->state == SS_DISCONNECTING) 
		{
			send_sig(SIGPIPE, current, 1);
			return(-EPIPE);
		}
		return(-EINVAL);
	}
	// 获取对端的unix_proto_data字段
	pupd = UN_DATA(sock)->peerupd;	/* safer than sock->conn */
	// 还有多少空间可写
	while(!(space = UN_BUF_SPACE(pupd))) 
	{
		sock->flags |= SO_NOSPACE;
		if (nonblock) 
			return(-EAGAIN);
		sock->flags &= ~SO_NOSPACE;
		interruptible_sleep_on(sock->wait);
		if (current->signal & ~current->blocked) 
		{
			return(-ERESTARTSYS);
		}
		if (sock->state == SS_DISCONNECTING) 
		{
			send_sig(SIGPIPE, current, 1);
			return(-EPIPE);
		}
	}

/*
 *	Copy from the user's buffer to the write buffer,
 *	watching for wraparound. Then we wake up the reader.
 */
   
	unix_lock(pupd);

	do 
	{
		int part, cando;

		if (space <= 0) 
		{
			printk("UNIX: write: SPACE IS NEGATIVE!!!n");
			send_sig(SIGKILL, current, 1);
			return(-EPIPE);
		}

		/*
		 *	We may become disconnected inside this loop, so watch
		 *	for it (peerupd is safe until we close).
		 */
		 
		if (sock->state == SS_DISCONNECTING) 
		{
			send_sig(SIGPIPE, current, 1);
			unix_unlock(pupd);
			return(-EPIPE);
		}
		// 需要写的比能写的多
		if ((cando = todo) > space) 
			cando = space;
		// 可写空间一部分在队头一部分在队尾,则先写队尾的,再写队头的
		if (cando >(part = BUF_SIZE - pupd->bp_head))
			cando = part;
	
		memcpy_fromfs(pupd->buf + pupd->bp_head, ubuf, cando);
		// 更新可写地址,可写空间减少,处理回环情况
		pupd->bp_head =(pupd->bp_head + cando) &(BUF_SIZE-1);
		// 更新用户的buf指针
		ubuf += cando;
		// 还需要写多少个字
		todo -= cando;
		if (sock->state == SS_CONNECTED)
		{
			wake_up_interruptible(sock->conn->wait);
			sock_wake_async(sock->conn, 1);
		}
		space = UN_BUF_SPACE(pupd);
	}
	while(todo && space);

	unix_unlock(pupd);
	return(size - todo);
}


版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/THEANARKH/article/details/85814037
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2019-10-26 23:01:11
  • 阅读 ( 864 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢