首先我们先要创建一个用于通信的结构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. Light of future-冲刺Day 4

    目录 1.SCRUM部分: 每个成员进度 SCRUM 会议的照片 签入记录 代码运行截图 用户浏览界面 管理员浏览界面 2.PM 报告: 时间表 燃尽图 任务总量变化曲线 每名成员的贡献比 归属班级 ...

  2. Docker 常用命令(.NET Core示例)

    Docker安装 CentOS Docker 安装 安装 Docker Desktop for Mac.Docker Desktop for Windows 设置docker仓库镜像加速器 迁移Doc ...

  3. MySQL InnoDB存储引擎体系架构 —— 索引高级

    转载地址:https://mp.weixin.qq.com/s/HNnzAgUtBoDhhJpsA0fjKQ 世界上只两件东西能震撼人们的心灵:一件是我们心中崇高的道德标准:另一件是我们头顶上灿烂的星 ...

  4. WordPress快速增加百度收录,加快网站内容抓取

    本文已同步到专业技术网站 www.sufaith.com, 该网站专注于前后端开发技术与经验分享, 包含Web开发.Nodejs.Python.Linux.IT资讯等板块. 利用百度站长平台提供的链接 ...

  5. Linux忘记密码解决方案

    Linux 忘记密码解决方法 很多朋友经常会忘记Linux系统的root密码,linux系统忘记root密码的情况该怎么办呢?重新安装系统吗?当然不用!进入单用户模式更改一下root密码即可. 步骤如 ...

  6. Linux c++ vim环境搭建系列(5)——vim使用

    5. 使用 5.1 快捷键及设置 5.1.1 光标移动 w : 正向移动到相邻单词的首字符 b : 逆向移动到相邻单词的首字符 e : 正向移动到相邻单词的尾字符 ge : 逆向移动到相邻单词的尾字符 ...

  7. std::string 字符串分割

    #include <iostream> #include <string> #include <vector> std::vector<std::string ...

  8. threejs创建地球

    上个月底,在朋友圈看到一个号称“这可能是地球上最美的h5”的分享,点进入后发现这个h5还很别致,思考了一会,决定要不高仿一个? 到今天为止,高仿基本完成, 线上地址 github地址 除了手机端的me ...

  9. delphi 捕捉全局异常错误的方法

    private     { Private declarations }   public   procedure GlobalExceptionHandler(Sender: TObject; E: ...

  10. 小程序运行时如何助力传统APP转型?

    小程序和H5或者RN有什么区别?优越性在哪里? 长期以来,移动互联网界一直在寻找一种既能获得Native原生的体验,又可以低门槛快速开发的技术.在这个过程中出现了很多尝试,例如React Native ...