一、socket绑定入口
1、示例代码
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr("0.0.0.0");
server_address.sin_port = htons(9734);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
2、绑定入口前面介绍了socket从库函数到内核的过程,其最终都是通过102号中断进入内核,所不同的是子中断号不同;对于绑定,其子中断号是2;和创建socket一样,绑定socket的处理函数都是:
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
unsigned long a[6];
unsigned long a0, a1;
int err;
if (copy_from_user(a, args, nargs[call]))
return -EFAULT;
a0 = a[0];
a1 = a[1];
switch (call) {
…...
case SYS_BIND:
err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
…...
}
根据子中断号,内核会执行sys_bind()函数来完成地址的绑定;二、绑定的具体过程sys_bind()函数如下,一起来分析一下它的主要过程:
asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];
int err, fput_needed;
// 1, 根据fd查找相应的socket结构
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
// 2, 将用户空间的地址结构拷贝到内核空间
err = move_addr_to_kernel(umyaddr, addrlen, address);
if (err >= 0) {
err = security_socket_bind(sock,
(struct sockaddr *)address,
addrlen);
if (!err)
// 3, 根据协议域及socket类型,调用相应的bind函数
err = sock->ops->bind(sock,
(struct sockaddr *)
address, addrlen);
}
fput_light(sock->file, fput_needed);
}
return err;
}
上面的过程中:1、根据fd找到相应的socket结构在创建socket的最后,会将socket结构与文件系统关联,并返回给应用程序与socket相关的文件描述符;这里是根据应用程序传递过来的文件描述符取得关联的socket结构;下面看看从fd取得socket结构的代码:
static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
{
struct file *file;
struct socket *sock;
*err = -EBADF;
file = fget_light(fd, fput_needed);
if (file) {
sock = sock_from_file(file, err);
if (sock)
return sock;
fput_light(file, *fput_needed);
}
return NULL;
}
再到fget_lignt()去看看:
struct file *fget_light(unsigned int fd, int *fput_needed)
{
struct file *file;
struct files_struct *files = current->files;
…...
file = fcheck_files(files, fd);
…...
return file;
}
这里current宏返回当前运行的进程的描述符,current->files返回当前进程的打开文件表;函数fcheck_files(files, fd)根据fd从打开文件表里取出相应的file结构变量;在创建socket中提到,file与socket关联,是通过file->private=socket完成的,因为获取到file结构变量后,也可以通过同样的方式取得socket结构变量;sock_from_file()函数就是用来完成此工作的;
2、将地址从用户空间拷贝到内核空间
1) 用户空间和内核空间的概念:
Linux内核管理模型中,简化了分段机制,使得虚拟地址与线性地址总是一致的;因此,针对32位的机器,Linux的虚拟地址空间也为0~4G。
Linux内核将这4G字节的空间分为两部分:将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”;而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间”。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。但是从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。
2) 用户态和内核态
当进程在执行用户自己的代码时,则称其处于用户运行态(用户态);即此时处理器在特权级最低的(3级)用户代码中运行;在用户态,进程使用进程的用户栈;
当进程执行系统调用而陷入内核代码中执行时,称该进程处于内核运行态(或简称为内核态),此时处理器处于特权级最高的(0级)内核代码中执行;当进程处于内核态时,执行的内核代码会使用当前进程的内核栈;每个进程都有自己的内核堆栈。
当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于内核态,因为中断处理程序将使用当前进程的内核栈,这与处于内核态的进程的状态有些类似。
3) 用户栈和内核栈
前面提到,每个进程有2个栈,即用户栈和内核栈;用户栈的空间指向用户地址空间,内核栈的空间指向内核地址空间。当进程在用户态运行时,CPU堆栈指针寄存器esp指向用户栈地址,使用用户栈;当进程运行在内核态时,CPU堆栈指针寄存器esp指向的是内核栈空间地址,使用的是内核栈;
内核在创建一个新的进程时,在创建进程控制块的同时,即创建了内核栈;而当进程调用execve的时候,才会创建用户栈;
4) 为什么要拷贝?
如果内核直接访问用户空间的地址,或是使用memcpy来拷贝,可能会出现缺页,但是缺页后的中断处理程序需要特定的结构辅助才能正常返回到缺页中断发生的地方,因此需要使用copy_from_user来完成;
结合上面提到的用户态、内核态、用户空间和地址空间后,就不难理解为什么系统调用中,都要将一些参数从用户空间拷贝到内核空间了;
3、地址结构
示例代码中创建的是类型为struct sockaddr_in的结构体变量,在调用bind()库函数时,将地址变量强制转化为struct sockaddr结构;
大家看到这里可能会有下面的疑问:
a) 这两个结构体到底是什么关系?
b) 为什么要强制转化为struct sockaddr结构?
c) bind()库函数最后一个参数,为什么要把结构体长度传进去呢?
首先看看struct sockaddr_in和struct sockaddr结构体吧:
struct sockaddr_in {
sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
这里struct sockaddr_in代表AF_INET域的地址,还有一个结构体struct sockaddr_un代表AF_UNIX域的地址;而struct sockaddr表示内核系统调用时使用的地址类型,内核根据不同的协议域,在处理具体地址时再转化为相应的结构体; 在struct sockaddr_in结构体中,__pad成员用于结构体的对齐,使struct sockaddr_in和struct sockaddr的大小一致;
未完,下篇blog待续;
分享到:
相关推荐
- 绑定地址:使用`bind()`函数将Socket绑定到特定的IP地址和端口号。 - 监听连接:对于服务器端,使用`listen()`函数设置最大连接队列长度,准备接受客户端连接。 - 接受连接:`accept()`函数用于接收客户端的...
Linux内核协议栈是操作系统核心的一部分,负责...通过深入研究Linux内核协议栈源码,不仅可以提升对网络协议的理解,还能为开发高效、稳定的网络应用提供理论支持。同时,这也有助于解决网络问题,提高系统的整体性能。
- `net/`目录下包含所有网络协议栈的源代码,如`tcp.c`处理TCP协议,`ipv4/`处理IPv4。 - `socket`层是用户空间与内核空间通信的接口,通过系统调用如`socket()`、`bind()`、`connect()`等实现。 - `sk_buff`...
准备将readme.md作为笔记,记录linux内核网络协议栈源码剖析的过程 天2,2016年11月18日20:24:07 增加了对sys_socket(), sock_create, __sock_create(), inet_create()的完全注释 socket的创建过程已经完成,接下来...
1. **Linux内核网络模型**: - Linux网络模型分为五层:数据链路层、网络层、传输层、会话层和应用层,这与OSI七层模型相对应。 - 内核网络子系统的核心是网络协议栈,它负责数据包的接收、处理和发送。 2. **...
《基于Linux网络协议栈实现及应用》这篇文献主要探讨了Linux操作系统中的TCP/IP协议栈的实现细节和网络编程模型。Linux以其强大的网络服务功能著称,支持多种网络协议,并能提供多种网络服务,如TCP/IP、IPX/SPX、...
3. **抽象数据结构**:在Linux协议栈的实现中,每一层都定义了一个抽象数据结构,如sk_buff(socket buffer),用于在不同协议层之间传递数据,提供统一的接口。这种设计使得上层协议可以透明地处理下层协议的具体...
Linux Socket服务器编程是网络通信的基础,它涉及到操作系统内核、网络协议栈以及C语言编程。在本实例中,我们将创建一个简单的TCP服务器,它监听客户端的连接请求,并在连接建立后,向客户端发送特定的字符串并关闭...
socket可以支持多种协议栈,如TCP/IP协议栈,使得不同主机间的进程能够通过网络进行通信。 #### 二、创建socket 创建socket是网络编程的第一步,主要通过`socket()`函数完成: ```c #include <sys/socket.h> int...
`sock` 结构体与特定的网络协议紧密相关,每个协议都有对应的 `sock` 实现。下面是 `sock` 结构体的部分关键字段及其功能: - **`__sk_common`**:与 `inet_timewait_sock` 共享使用,这部分包含了基本的协议信息。...
2. **协议兼容性**:不同操作系统支持多种网络协议,这些协议的工作方式和地址格式各异,因此需要一种统一的通信机制。 **TCP/IP协议族**通过引入IP地址、传输层协议(如TCP或UDP)和端口号这三种元素的组合来唯一...
Linux Socket是操作系统提供的网络通信接口,它允许应用程序通过网络协议(如TCP/IP)进行通信。本文将深入探讨Linux Socket的工作原理,主要包括以下几个方面:socket发送数据的函数流程、sys_socket流程、以及...
5. SOCKET 的位置:在应用层程序中,SOCKET 位于 IP 数据链路层和物理层之间,负责将应用层数据转换为网络数据。 6. 不同操作系统中的 SOCKET:包括 Windows Socket(Winsock)和 Linux Socket(BSD Socket)。 ...