Socket与系统调用深度分析

Socket与系统调用深度分析

实验环境:Linux-5.0.1 内核 32位系统的MenuOS

本文主要解决两个问题

  • 用户态如何通过中断进入socket的系统调用
  • socket抽象层如何通过多态的机制,来支持不同的传输层的协议。也就是socket作为父类,TCP/UDP为子类,父类指向子类对象,实现多态。

主要问题有下图的红色字体标出

<img src="https://img2018.cnblogs.com/blog/957240/201912/957240-20191219153934931-1462062478.png" width="500" height="400">
1#include<socket.h> 2int main(int argc, char *argv[]) 3{ ... 4 socket(...) 5 ... 6 return 0; 7} 8

该函数会调用socket,socket内核提供给我们的函数,要通过系统调用来使用。

调用流程

那么,在应用程序内,调用一个系统调用的流程是怎样的呢?

我们以一个假设的系统调用 xyz 为例,介绍一次系统调用的所有环节。

<img src="https://img2018.cnblogs.com/blog/957240/201912/957240-20191219154609677-335600228.png" width="500" height="300">

如上图,系统调用执行的流程如下:

  1. 应用程序 代码调用系统调用( xyz ),该函数是一个包装系统调用的 库函数 ;
  2. 库函数 ( xyz )负责准备向内核传递的参数,并触发 软中断 以切换到内核;
  3. CPU 被 软中断 打断后,执行 中断处理函数 ,即 系统调用处理函数 ( system_call);
  4. 系统调用处理函数 调用 系统调用服务例程 ( sys_xyz ),真正开始处理该系统调用;

所以如果要找到socket的系统调用,关键就是找到socket的system_call入口,在Linux-5.0.1 内核中

https://github.com/mengning/linux/blob/master/arch/x86/entry/syscalls/syscall_32.tbl 从中可以发现socket的系统调用位102号

<img src="https://img2018.cnblogs.com/blog/957240/201912/957240-20191219154304762-164213308.png" width="500" height="100">

那么跟踪程序如果可以跟踪到socketcall,就可以观察到系统调用,下面通过gdb调试qume中的linux源代码来跟踪系统调用。

跟踪socketcall系统调用

​

1#启动qemu 加载内核,不加-S,因为跟踪的系统调用,不是启动过程 2qemu -kernel ../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append nokaslr -s 3#打开一个新的终端 4gdb 5file ~/linux-5.0.1/vmlinux 6b sys_socketcall 7target remote:1234 8c 9#在qume中输入 10replyhi

<img src="https://img2018.cnblogs.com/blog/957240/201912/957240-20191219154409452-901642349.png" width="600" height="200"> <img src="https://img2018.cnblogs.com/blog/957240/201912/957240-20191219154502167-2084708389.png" width="600" height="400">

可以看到程序停止在_sys_socketcall,该韩式纸SYSCALL_DEFINE2,也即是系统调用后,通过软中断,跳转到了该函数,查看该函数源码如下,gdb中的call = 1 ,正是传递到swich case语句中

1SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) 2{ 3 unsigned long a[AUDITSC_ARGS]; 4 unsigned long a0, a1; 5 int err; 6 unsigned int len; 7 8 if (call < 1 || call > SYS_SENDMMSG) 9 return -EINVAL; 10 call = array_index_nospec(call, SYS_SENDMMSG + 1); 11 12 len = nargs[call]; 13 if (len > sizeof(a)) 14 return -EINVAL; 15 16 /* copy_from_user should be SMP safe. */ 17 if (copy_from_user(a, args, len)) 18 return -EFAULT; 19 20 err = audit_socketcall(nargs[call] / sizeof(unsigned long), a); 21 if (err) 22 return err; 23 24 a0 = a[0]; 25 a1 = a[1]; 26 27 switch (call) { //根据call的不同之,从而执行不同的系统系统调用 当前call = 1 也就是执行 28 case SYS_SOCKET: // SYS_SOCKET的系统调用。 29 err = __sys_socket(a0, a1, a[2]); 30 break; 31 case SYS_BIND: 32 err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]); 33 break; 34 case SYS_CONNECT: 35 err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]); 36 break; 37 case SYS_LISTEN: 38 err = __sys_listen(a0, a1); 39 break; 40 case SYS_ACCEPT: 41 err = __sys_accept4(a0, (struct sockaddr __user *)a1, 42 (int __user *)a[2], 0); 43 break; 44 case SYS_GETSOCKNAME: 45 err = 46 __sys_getsockname(a0, (struct sockaddr __user *)a1, 47 (int __user *)a[2]); 48 break; 49 case SYS_GETPEERNAME: 50 err = 51 __sys_getpeername(a0, (struct sockaddr __user *)a1, 52 (int __user *)a[2]); 53 break; 54 case SYS_SOCKETPAIR: 55 err = __sys_socketpair(a0, a1, a[2], (int __user *)a[3]); 56 break; 57 case SYS_SEND: 58 err = __sys_sendto(a0, (void __user *)a1, a[2], a[3], 59 NULL, 0); 60 break; 61 case SYS_SENDTO: 62 err = __sys_sendto(a0, (void __user *)a1, a[2], a[3], 63 (struct sockaddr __user *)a[4], a[5]); 64 break; 65 case SYS_RECV: 66 err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3], 67 NULL, NULL); 68 break; 69 case SYS_RECVFROM: 70 err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3], 71 (struct sockaddr __user *)a[4], 72 (int __user *)a[5]); 73 break; 74 case SYS_SHUTDOWN: 75 err = __sys_shutdown(a0, a1); 76 break; 77 case SYS_SETSOCKOPT: 78 err = __sys_setsockopt(a0, a1, a[2], (char __user *)a[3], 79 a[4]); 80 break; 81 case SYS_GETSOCKOPT: 82 err = 83 __sys_getsockopt(a0, a1, a[2], (char __user *)a[3], 84 (int __user *)a[4]); 85 break; 86 case SYS_SENDMSG: 87 err = __sys_sendmsg(a0, (struct user_msghdr __user *)a1, 88 a[2], true); 89 break; 90 case SYS_SENDMMSG: 91 err = __sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2], 92 a[3], true); 93 break; 94 case SYS_RECVMSG: 95 err = __sys_recvmsg(a0, (struct user_msghdr __user *)a1, 96 a[2], true); 97 break; 98 case SYS_RECVMMSG: 99 if (IS_ENABLED(CONFIG_64BIT) || !IS_ENABLED(CONFIG_64BIT_TIME)) 100 err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1, 101 a[2], a[3], 102 (struct __kernel_timespec __user *)a[4], 103 NULL); 104 else 105 err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1, 106 a[2], a[3], NULL, 107 (struct old_timespec32 __user *)a[4]); 108 break; 109 case SYS_ACCEPT4: 110 err = __sys_accept4(a0, (struct sockaddr __user *)a1, 111 (int __user *)a[2], a[3]); 112 break; 113 default: 114 err = -EINVAL; 115 break; 116 } 117 return err; 118}

在linuxnet的lab3中查看replyhi源码,可以发现其实就是基本的socket - bind - listen - accept 过程这就不详细列出,结合上面的_sys_socketcall,可以猜想到会调用多次_sys_socketcall,进入switch case语句中。

<img src="https://img2018.cnblogs.com/blog/957240/201912/957240-20191219155159722-576966221.png" width="600" height="400">

可以看到,调用了4次,分别call为 1 2 4 5,也对用服务器开始动作 socket - bind - listen -accept

客户端类似,这里不做演示。

###socket抽象层的多态的机制

从上上面的SYSCALL_DEFINE2中可以看到__sys_socket()函数

1SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args){ 2、、、 3switch (call) { 4 case SYS_SOCKET: 5 err = __sys_socket(a0, a1, a[2]); 6 break; 7、、、 8} 9 10 11//进入该函数 12int __sys_socket(int family, int type, int protocol) 13{ 14 int retval; 15 struct socket *sock; // socket位关键的数据结构 16 int flags; 17 18 /* Check the SOCK_* constants for consistency. */ 19 BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC); 20 BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK); 21 BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK); 22 BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK); 23 24 flags = type & ~SOCK_TYPE_MASK; 25 if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) 26 return -EINVAL; 27 type &= SOCK_TYPE_MASK; 28 29 if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK)) 30 flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; 31 32 retval = sock_create(family, type, protocol, &sock); 33 if (retval < 0) 34 return retval; 35 36 return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK)); 37} 38 39 40<net.h> 41struct socket { 42 socket_state state; 43 short type; 44 unsigned long flags; 45 struct socket_wq *wq; 46 struct file *file; 47 struct sock *sk; 48 const struct proto_ops *ops; // *** 49}; 50

其中最为核心的就是_struct proto_ops_,查看它的定义可以看到

1<net.h> 2struct proto_ops { 3 int family; 4 struct module *owner; 5 int (*release) (struct socket *sock); 6 int (*bind) (struct socket *sock, 7 struct sockaddr *myaddr, 8 int sockaddr_len); 9 int (*connect) (struct socket *sock, 10 struct sockaddr *vaddr, 11 int sockaddr_len, int flags); 12 int (*socketpair)(struct socket *sock1, 13 struct socket *sock2); 14 int (*accept) (struct socket *sock, 15 struct socket *newsock, int flags, bool kern); 16 //...

可以这个结构体中有很多的函数指针,查看SYSCALL_DEFINE2中的__sys_connect

1int __sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen) 2{ 3 struct socket *sock; 4 struct sockaddr_storage address; 5 int err, fput_needed; 6 7 sock = sockfd_lookup_light(fd, &err, &fput_needed); 8 if (!sock) 9 goto out; 10 err = move_addr_to_kernel(uservaddr, addrlen, &address); 11 if (err < 0) 12 goto out_put; 13 14 err = 15 security_socket_connect(sock, (struct sockaddr *)&address, addrlen); 16 if (err) 17 goto out_put; 18 19 err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen, 20 sock->file->f_flags) //***

可以看到有一个sock->ops->connect函数指针的调用,我们这是调用的是TCP,查看ipv4/tcp_ipv4.c

1struct proto tcp_prot = { 2 .name = "TCP", 3 .owner = THIS_MODULE, 4 .close = tcp_close, 5 .pre_connect = tcp_v4_pre_connect, 6 .connect = tcp_v4_connect, 7 .disconnect = tcp_disconnect, 8 .accept = inet_csk_accept, 9 .ioctl = tcp_ioctl, 10 .init = tcp_v4_init_sock, 11 //...

这就是tcp中对对相应函数指针实现的初始化。调用时就会通过这个数据结构查到相应的函数。

不同的协议,就痛过不同的数据结构,写入不同的函数指针,从而实现了多态的机制。

参考资料

https://www.cnblogs.com/fasionchan/p/9431784.html

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java将前端的json数组字符串转换为列表

记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang

Socket与系统调用深度分析 - HelloWorld