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的源码. @ ...
随机推荐
- 【php】LAMP中开启错误提示
vi /usr/local/php/etc/php.ini 查找display_errors 开启On即可
- How to generate entities from database schema using doctrine-orm-module
1.安装好doctrine,在composer.json中添加如下 "require": { "php": "^5.6 || ^7.0", ...
- Linux 磁盘管理篇, 目录管理(二)
格式化档案系统: mke2fs 列出文件系统的整体磁盘使用量 df 评估文件系统的磁盘使用量 du 查看Superbl ...
- 23.1 abstract抽象类案例
package day2_抽象类; /* * 基础班老湿,就业班老湿 * 共性 * 属性 姓名,年龄,性别 * 行为 讲课 */ public class AbstractTeacherTest { ...
- Java并发之显式锁和隐式锁的区别
Java并发之显式锁和隐式锁的区别 在面试的过程中有可能会问到:在Java并发编程中,锁有两种实现:使用隐式锁和使用显示锁分别是什么?两者的区别是什么?所谓的显式锁和隐式锁的区别也就是说说Synchr ...
- Python设计模式(11)-状态模式
# coding=utf-8 # *状态模式:一个方法的判断逻辑太长,就不容易修改.方法过长,其本质就是,# * 就是本类在不同条件下的状态转移.状态模式,就是将这些判断分开到各个能# * 表示当前状 ...
- 泛型方法或泛型类中的方法是内部调用、PInvoke 或是在 COM 导入类中定义的。
泛型基类中引用Api函数定义时static extern,在子类中会提示: 未处理TypeLoadException 泛型方法或泛型类中的方法是内部调用.PInvoke 或是在 COM 导入类中定义的 ...
- codeforces Equalizing by Division (easy version)
output standard output The only difference between easy and hard versions is the number of elements ...
- Python实现按键精灵(一)-键鼠操作
需要安装 pywin32库 pip install pywin32 import win32api import time #鼠标移动 def mouse_move(x,y): win32api.Se ...
- 20200107——记spring的DataSource
spring项目中总要跟数据库打交道,其中怎么连接数据库的方法都有很多,大概分为3类: 1) 通过JNDI获取应用服务器(如JBOSS, Tomcat) 的数据源 2) Spring容器中直接配置数 ...