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\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域源码解析的更多相关文章
- spring MVC cors跨域实现源码解析
# spring MVC cors跨域实现源码解析 > 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议.IP.http方法任意一个不同就 ...
- spring MVC cors跨域实现源码解析 CorsConfiguration UrlBasedCorsConfigurationSource
spring MVC cors跨域实现源码解析 spring MVC cors跨域实现源码解析 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议 ...
- iOS即时通讯之CocoaAsyncSocket源码解析二
原文 前言 本文承接上文:iOS即时通讯之CocoaAsyncSocket源码解析一 上文我们提到了GCDAsyncSocket的初始化,以及最终connect之前的准备工作,包括一些错误检查:本机地 ...
- iOS即时通讯之CocoaAsyncSocket源码解析一
申明:本文内容属于转载整理,原文连接 前言: CocoaAsyncSocket是谷歌的开发者,基于BSD-Socket写的一个IM框架,它给Mac和iOS提供了易于使用的.强大的异步套接字库,向上封装 ...
- Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
概要 这一章,我们对TreeMap进行学习.我们先对TreeMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeMap.内容包括:第1部分 TreeMap介绍第2部分 TreeMa ...
- OKHttp源码解析
http://frodoking.github.io/2015/03/12/android-okhttp/ Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 A ...
- jQuery2.x源码解析(DOM操作篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) jQuery这个类库最为核心重要的功能就是DOM ...
- 从源码解析LinkedList集合
上篇文章我们介绍了ArrayList类的基本的使用及其内部的一些方法的实现原理,但是这种集合类型虽然可以随机访问数据,但是如果需要删除中间的元素就需要移动一半的元素的位置,效率低下.并且它内 ...
- @GeneratedValue源码解析
JPA要求每一个实体必须有且只有一个主键,而@GeneratedValue提供了主键的生成策略,这就是@GeneratedValue注解存在的意义.本文将浅析@GeneratedValue的源码. @ ...
随机推荐
- Html 慕课园编程练习10-1
23:10:25 2019-08-14 自己写的这个好丑.... 题目:利用之前我们学过的JavaScript知识,实现选项卡切换的效果. 效果图: (另外 这个动图是怎么插入的 用url就行 复制就 ...
- php __DIR__ 解释下
__DIR__, php5.3 才增加的这个魔术常量,表示当前文件所在的目录地址. php5.3之前用dirname(__FILE__);表示__DIR__; __FILE__这个表示当前文件的路径.
- java接口自动化(三) - 手工接口测试到自动化框架设计之鸟枪换炮
1.简介 上一篇宏哥介绍完了接口用例设计,那么这一章节,宏哥就趁热打铁介绍一下,接口测试工具.然后小伙伴们或者童鞋们就可以用接口测试工具按照设计好的测试用例开始执行用例进行接口手动测试了.关于手动测试 ...
- django-rest-framework视图和url
django-rest-framework视图 GenericView class GenericView(GenericAPIView): queryset = models.Role.object ...
- flask-类视图
flask-类视图 标准类视图 from flask import Flask, render_template, views, jsonify app = Flask(__name__) class ...
- 这可能是 Github 上最全面的 Flutter 教程
引语 晚上好,我是猫咪,我的公众号「程序媛猫咪」会推荐 GitHub 上好玩的项目,挖掘开源的价值,欢迎关注我. 刚下班到家,金三银四,虽然今天行情尤其地不好,但身边的同事也是走了一波,不免会受到影响 ...
- k8s Service学习
service的概念 kubernetes service定义了一个抽象概念,一个pod的逻辑分组,一种可以访问的策略---通常称为服务.这组pod能够被service访问到,通常通过label se ...
- [leetcode]1379. Find a Corresponding Node of a Binary Tree in a Clone of That Tree
[leetcode]1379. Find a Corresponding Node of a Binary Tree in a Clone of That Tree 链接 leetcode 描述 ...
- 原生js实现扇形导航以及动画的坑
第一次发博客,有点紧张.首先来一张效果图. 主要是实现了点击右下角的风扇按钮实现了: 导航栏的开启与关闭,中间伴随着 transition过渡以及transform的2D动画. 上源码: <!D ...
- 05-移动web之流式布局
一.视口 1.常见屏幕知识 设备 解释 描述 宽 屏幕的宽度 - (单位:英寸) 屏幕的宽度 高 屏幕的高度 -(单位:英寸) 屏幕的高度 对角线 屏幕的对角线的长度 英寸 一般说手机尺寸 是指以屏幕 ...