unix域源码解析
阅读原文时间:2023年07月10日阅读:4
首先我们先要创建一个用于通信的结构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\n");
        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] = '\0';
    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] = '\0';
    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\n", 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] = '\0';
    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);
}

复制代码

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章