首先我们先要创建一个用于通信的结构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);
} 复制代码

unix域源码解析的更多相关文章

  1. spring MVC cors跨域实现源码解析

    # spring MVC cors跨域实现源码解析 > 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议.IP.http方法任意一个不同就 ...

  2. spring MVC cors跨域实现源码解析 CorsConfiguration UrlBasedCorsConfigurationSource

    spring MVC cors跨域实现源码解析 spring MVC cors跨域实现源码解析 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议 ...

  3. iOS即时通讯之CocoaAsyncSocket源码解析二

    原文 前言 本文承接上文:iOS即时通讯之CocoaAsyncSocket源码解析一 上文我们提到了GCDAsyncSocket的初始化,以及最终connect之前的准备工作,包括一些错误检查:本机地 ...

  4. iOS即时通讯之CocoaAsyncSocket源码解析一

    申明:本文内容属于转载整理,原文连接 前言: CocoaAsyncSocket是谷歌的开发者,基于BSD-Socket写的一个IM框架,它给Mac和iOS提供了易于使用的.强大的异步套接字库,向上封装 ...

  5. Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例

    概要 这一章,我们对TreeMap进行学习.我们先对TreeMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeMap.内容包括:第1部分 TreeMap介绍第2部分 TreeMa ...

  6. OKHttp源码解析

    http://frodoking.github.io/2015/03/12/android-okhttp/ Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 A ...

  7. jQuery2.x源码解析(DOM操作篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) jQuery这个类库最为核心重要的功能就是DOM ...

  8. 从源码解析LinkedList集合

         上篇文章我们介绍了ArrayList类的基本的使用及其内部的一些方法的实现原理,但是这种集合类型虽然可以随机访问数据,但是如果需要删除中间的元素就需要移动一半的元素的位置,效率低下.并且它内 ...

  9. @GeneratedValue源码解析

    JPA要求每一个实体必须有且只有一个主键,而@GeneratedValue提供了主键的生成策略,这就是@GeneratedValue注解存在的意义.本文将浅析@GeneratedValue的源码. @ ...

随机推荐

  1. 【php】LAMP中开启错误提示

    vi /usr/local/php/etc/php.ini 查找display_errors 开启On即可

  2. How to generate entities from database schema using doctrine-orm-module

    1.安装好doctrine,在composer.json中添加如下 "require": { "php": "^5.6 || ^7.0", ...

  3. Linux 磁盘管理篇, 目录管理(二)

    格式化档案系统:                    mke2fs 列出文件系统的整体磁盘使用量            df 评估文件系统的磁盘使用量            du 查看Superbl ...

  4. 23.1 abstract抽象类案例

    package day2_抽象类; /* * 基础班老湿,就业班老湿 * 共性 * 属性 姓名,年龄,性别 * 行为 讲课 */ public class AbstractTeacherTest { ...

  5. Java并发之显式锁和隐式锁的区别

    Java并发之显式锁和隐式锁的区别 在面试的过程中有可能会问到:在Java并发编程中,锁有两种实现:使用隐式锁和使用显示锁分别是什么?两者的区别是什么?所谓的显式锁和隐式锁的区别也就是说说Synchr ...

  6. Python设计模式(11)-状态模式

    # coding=utf-8 # *状态模式:一个方法的判断逻辑太长,就不容易修改.方法过长,其本质就是,# * 就是本类在不同条件下的状态转移.状态模式,就是将这些判断分开到各个能# * 表示当前状 ...

  7. 泛型方法或泛型类中的方法是内部调用、PInvoke 或是在 COM 导入类中定义的。

    泛型基类中引用Api函数定义时static extern,在子类中会提示: 未处理TypeLoadException 泛型方法或泛型类中的方法是内部调用.PInvoke 或是在 COM 导入类中定义的 ...

  8. codeforces Equalizing by Division (easy version)

    output standard output The only difference between easy and hard versions is the number of elements ...

  9. Python实现按键精灵(一)-键鼠操作

    需要安装 pywin32库 pip install pywin32 import win32api import time #鼠标移动 def mouse_move(x,y): win32api.Se ...

  10. 20200107——记spring的DataSource

    spring项目中总要跟数据库打交道,其中怎么连接数据库的方法都有很多,大概分为3类: 1) 通过JNDI获取应用服务器(如JBOSS, Tomcat) 的数据源 2)  Spring容器中直接配置数 ...