• 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 buffer
    ");
    		return(-ENOMEM);
    	}
    	// 给unix_proto_data的buf字段分配一个页大小的内存
    	if (!(upd->buf = (char*) get_free_page(GFP_USER))) 
    	{
    		printk("UNIX: create: can't get page!
    ");
    		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!
    ");*/
    		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 %s
    ", 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!!!
    ");
    			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!!!
    ");
    			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);
    }
    
    
    复制代码


  • 相关阅读:
    校验器
    Mybatis分页中遇到的坑3

    Lock1
    Validation(4)-临时
    在Java中如何判断对象已死?
    垃圾回收算法的种类
    Java内存区域
    Java 中的 volatile关键字含义
    分别写出堆内存溢出与栈内存溢出的程序?
  • 原文地址:https://www.cnblogs.com/twodog/p/12135255.html
Copyright © 2020-2023  润新知