1 重新搭建实验环境

前面都是用实验楼环境做的实验,偷的懒总是要还的,这一次重装环境前后花了十几个小时,踩了无数的坑。

1.1 Ubuntu和LINUX内核的区别

Ubuntu是基于LINUX内核编写的一个操作系统。LINUX内核定义了一些基本的系统功能,Ubuntu在内核之上加入了图形界面,包管理等功能,优化了人机交互。本次实验,要求使用LINUX内核5.0以上,所以,在下载安装完Ubuntu系统后,需要对内核进行更新。

$ uname -a

上面这个指令会显示Ubuntu当前的内核版本,我们可以通过它来观察内核的升级是否成功。

1.2 从零开始

下载安装Ubuntu

首先到Ubuntu官网上下载一个Ubuntu镜像,但是太慢了,我们可以在国内的镜像网站上去下载。指路网易镜像

下载完成后,在VMware虚拟机中进行系统安装,没什么可说的。

  • 设置超级管理员

    新装的系统没有超级管理员,所以需要先设置一个。执行下面的命令,按照提示要求完成管理员注册。
sudo passwd root
  • 设置共享文件夹

    为了方便VMware内虚拟主机和我们的主机进行交互,可以设置一个共享文件夹

    首先将虚拟主机关机,然后在虚拟机设置=》选项卡中设置共享文件夹。安装VMwareTools,在VMware菜单栏,点击“重新安装VMwareTools”。虚拟主机内会出现资源管理器,里面有下载好的压缩包,将它拷贝到桌面上解压。然后执行VMware底部弹出的建议命令,完成安装。查看共享文件夹。共享文件夹的位置在/mnt/hfgs/share/

更换国内源

国外的资源下载速度实在太慢,所以在开始工作之前,建议先更换成国内镜像,指路科大镜像

  • 备份原始源
$ sudo cp /etc/apt/sources.list /etc/apt/sources_backup.list
  • 修改配置文件
$ sudo gedit /etc/apt/sources.list

把从网上找到的资源列表复制拷贝过来,点击资源管理器右上角的save按钮

  • 更新源
$ sudo apt-get update

下载编译LINUX5.0内核

先下载5.0以上linux内核。

  • 解压内核文件
$ xz -d linux-5.0.1.tar.xz
$ tar -xvf linux-5.0.1.tar
  • 安装依赖
$ sudo apt-get install build-essential
$ sudo apt-get install libelf-dev
$ sudo apt-get install libncurses-dev
$ sudo apt-get install flex
$ sudo apt-get install bison
$ sudo apt-get install libssl-dev
  • 配置内核
$ cd /linux/5.0.1
$ sudo cp /boot/config-5.0.23-generic -r .config
$ sudo make oldconfig
$ sudo make localmodconfig
$ make menuconfig

在弹出的图形化界面中配置

kernel hacking -> compile-time and compiler options 勾选 [*] compiler the kernel with debug info

  • 编译内核
$ sudo make
$ sudo make modules_install
# 更新
$ sudo make install
  • 重启虚拟机

    查看内核版本是否已经是5.0.1
$ uname -a

1.3 搭建实验环境

  • 安装qemu模拟命令,加载linux内核
$ sudo apt install qemu
$ qemu-sysem-x86_64 -kernel linux-5.0.1/arch/x86_64/boot/bzIamge
  • 剩余的部分主要是配置qemu环境,把写好的replyhi网络聊天程序集成到qemu中,和上一次实验内容相同,不再重复演示。

2 Socket系统调用分析

按照实验要求,我们分为两个方向来研究Socket系统调用。实验指出,内核将系统调用作为一个特殊中断来处理,因此首先我们对这一点进行验证;其次我们将探究,对于不同的协议,Socket系统调用源码中是如何封装协议细节的,是否使用了实验提到的“多态”机制,怎么实现的。

2.1 系统调用的中断实现

修改Makefile

为探究64位程序中socket的系统调用行为,我们首先需要对上一节使用到的Makefile进行修改

#
# Makefile for linuxnet/lab3
#
# ... 省略前文 rootfs:
gcc -o init linktable.c menu.c main.c -m64 -static -lpthread
find init | cpio -o -Hnewc |gzip -9 > ../../rootfs.img
qemu-system-x86_64 -kernel ../../linux-5.0.1/arch/x86/boot/bzImage -initrd ../../rootfs.img -append nokaslr -s -S # ...省略后文

在编译指令gcc那一行,将编译选项由-m32改为-m64

执行指令

$ make rootfs

我们得到了新的64位可执行文件init

GDB调试

使用GDB调试init,在socket函数前打上断点。

$ gdb init
$ (gdb) break socket

打开汇编窗口,查看代码运行情况

$ (gdb) layout asm

可以看到,程序在socket函数入口处停下,下一条汇编指令是一个syscall的系统调用。

反汇编init

init进行反汇编

$ objdump -d init > init_ASM.txt

查看init_ASM.txt文件,在第104553行找到socket对应的系统调用。

证明对于socket api的调用是通过socketcall这个特殊中断来实现的。

syscall的具体实现

利用同样的办法,我们按照上一节的方法启动qemu进行远程调试,设置如下断点:

$ (gdb) break sys_socketcall

跟踪到一个关键函数:SYSCALL_DEFINE2(),它位于linux-5.0.1/net/socket.c之中。

关键代码如下:

switch (call) {
case SYS_SOCKET:
err = __sys_socket(a0, a1, a[2]);
break;
case SYS_BIND:
err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
break;
case SYS_CONNECT:
err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
break;
case SYS_LISTEN:
err = __sys_listen(a0, a1);
break;
case SYS_ACCEPT:
err = __sys_accept4(a0, (struct sockaddr __user *)a1,
(int __user *)a[2], 0);
break;
// ... 省略其余部分
}

可见,每次socket都会调用同一个函数,通过传入的call值不同,在分支语句中执行对应的系统服务例程。

2.2 Socket封装网络协议的多态机制

__sys_socket()为例,其源码位于同一文件下,也是C语言实现的:

int __sys_socket(int family, int type, int protocol)
{
int retval;
struct socket *sock;
int flags; /* Check the SOCK_* constants for consistency. */
BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK); flags = type & ~SOCK_TYPE_MASK;
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
type &= SOCK_TYPE_MASK; if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
return retval; return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}

注意到函数的传入参数中有一个protocol变量,它用来指定传入的协议是多少。对于系统底层来说,不同的protocol值对应不同的协议类型,而对于socket通信来说,它只负责从高层接受这个字段值,然后交付更底层的函数,在这里,调用到的sock_create代码如下:

int __sock_create(struct net *net, int family, int type, int protocol,
struct socket **res, int kern)
{
int err;
struct socket *sock;
const struct net_proto_family *pf; /*
* Check protocol is in range
*/
if (family < 0 || family >= NPROTO)
return -EAFNOSUPPORT;
if (type < 0 || type >= SOCK_MAX)
return -EINVAL; /* Compatibility. This uglymoron is moved from INET layer to here to avoid
deadlock in module load.
*/
if (family == PF_INET && type == SOCK_PACKET) {
pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
current->comm);
family = PF_PACKET;
} err = security_socket_create(family, type, protocol, kern);
if (err)
return err;
// 省略后文

可以发现这个函数仍然不是最底层的函数,它根据情况继续调用security_socket_creat(),或者返回协议错误信息。

从代码上来看,Socket封装协议细节,使用到的应该是名为socket的结构体,在__sys_bind()等函数中,协议字段作为地址长度被传入,说明对于socket来说是通过判断协议字段长度来区分ipv4和ipv6两种不同协议的。在socket结构体中,有一个名为sk_family的字段,通过它的取值不同来判断这个socket是使用ipv4还是ipv6。可以从socket.c中的代码印证这一点:

/* This routine returns the IP overhead imposed by a socket i.e.
* the length of the underlying IP header, depending on whether
* this is an IPv4 or IPv6 socket and the length from IP options turned
* on at the socket. Assumes that the caller has a lock on the socket.
*/
u32 kernel_sock_ip_overhead(struct sock *sk)
{
struct inet_sock *inet;
struct ip_options_rcu *opt;
u32 overhead = 0;
#if IS_ENABLED(CONFIG_IPV6)
struct ipv6_pinfo *np;
struct ipv6_txoptions *optv6 = NULL;
#endif /* IS_ENABLED(CONFIG_IPV6) */ if (!sk)
return overhead; switch (sk->sk_family) {
case AF_INET:
inet = inet_sk(sk);
overhead += sizeof(struct iphdr);
opt = rcu_dereference_protected(inet->inet_opt,
sock_owned_by_user(sk));
if (opt)
overhead += opt->opt.optlen;
return overhead;
#if IS_ENABLED(CONFIG_IPV6)
case AF_INET6:
np = inet6_sk(sk);
overhead += sizeof(struct ipv6hdr);
if (np)
optv6 = rcu_dereference_protected(np->opt,
sock_owned_by_user(sk));
if (optv6)
overhead += (optv6->opt_flen + optv6->opt_nflen);
return overhead;
#endif /* IS_ENABLED(CONFIG_IPV6) */
default: /* Returns 0 overhead if the socket is not ipv4 or ipv6 */
return overhead;
}
}
EXPORT_SYMBOL(kernel_sock_ip_overhead);

综上所述,socket实现了协议封装的多态,它通过结构体的形式,用协议字段的长度作为划分协议的依据,以此将ipv4和ipv6区分开来。而对于调用这些函数和api的高层来说,不管自己是什么协议都调用同样的函数。

从零开始—Socket系统调用和多态封装的更多相关文章

  1. python面向对象之继承/多态/封装

    老师说,按继承/多态/封装这个顺序来讲. 子类使用父类的方法: #!/usr/bin/env python # coding:utf-8 class Vehicle: def __init__(sel ...

  2. 组合&多态&封装

    目录 组合&多态&封装 一.组合 1.1什么是组合 1.2 为什么要用组合 1.3 如何使用组合 1.4 继承和组合都在什么时候用 二.多态与多态性 2.1 什么是多态 2.2 如何用 ...

  3. 继承 & 多态 & 封装

    什么是继承 继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类 python中类的继承分为:单继承和多继承 class Par ...

  4. python面向对象 : 抽象类(接口类),多态,封装(私有制封装)

    一. 抽象类(接口类) 与java一样, python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类, 它的特殊之处在于只能被继承, 不能被实例化. 从设计角度去看, 如果类是从现实对 ...

  5. day25类的组合多态封装

    类的组合多态与封装类的组合 1. 什么是组合  组合指的是某一个对象拥有一个属性,该属性的值是另外一个类的对象 2. 为何要用组合  通过为某一个对象添加属性(属性的值是另外一个类的对象)的方式,可以 ...

  6. 【学习笔记】--- 老男孩学Python,day18 面向对象------抽象类(接口类), 多态, 封装

    抽象类,接口类 Python没有接口这个概念 抽象类(接口类): 目的是制定一个规范 要学会归一化设计,有重复的东西就要想把它们合并起来 from abc import ABCMeta, abstra ...

  7. socket系统调用

    SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) { int retval; struct socket *sock; in ...

  8. python oop常用术语 继承 多态 封装

    面向对象优点 1.通过封装明确了内外 2.通过继承+多态在语言层面支持了归一化设计 抽象/实现 抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现 ...

  9. python day - 19 抽象类 接口类 多态 封装

    一. 抽象类接口类即制定一个规范 特点: 1.不可被实例化. 2.规范子类当中必须事先某个方法. 3.在python中有原生实现抽象类的方法,但没有原生实现接口类的方法. 例题:制定一个规范就是,子类 ...

随机推荐

  1. 自制window下core animation引擎 - demo第二弹 - 仿QQ电脑管家加速小火箭

    一年前想写一个像cocoa那样,可以方便层动画开发的引擎,写着写着又逆向它的QuartzCore.framework,也就是CoreAnimation的底层,已经大半年没有搞windows这个引擎.大 ...

  2. flex一些属性

    // 改变主轴的方向 flex-direction: column; // display:flex的子元素无法设置宽度 // 子元素有个flex-shrink属性,表示在父元素宽度不够的情况下是否自 ...

  3. firefox浏览器中使用vux的x-input报错TypeError: _this3.$refs.input.scrollIntoViewIfNeeded is not a function

    最近做公众号项目,想着统一风格,所以决定使用vux. 在调试时发现,只要鼠标点击x-input输入框,就会报错 TypeError: _this3.$refs.input.scrollIntoView ...

  4. JSAPI 基于arcgis_js_api3.3的部署

    JSAPI,即ArcGIS API For JavaScript,是arcgis基于JavaScript环境下的开发包.包含Dojo框架. ||Dojo结构如下:=================== ...

  5. Vue过渡动画运用transition

    vue的过渡动画,主要是transition标签的使用,配合css动画实现的.官方文档css过渡 通过点击事件来切换show的值来改变显示的文本,下面的css通过进入离开时的在匀速状态下xxs(秒)下 ...

  6. Java流程控制之(三)嵌套

    目录 嵌套循环 for循环嵌套 while循环嵌套 总结 之前谈到各种循环结构,有for循环啊,有while循环啊,可以完成不断重复的动作,相当方便.那么如果好多个循环结合再一次,又是如何实现效果的呢 ...

  7. [FPGA]Verilog利用PWM调制巧妙完成RGB三色彩虹呼吸灯(给简约的题目以美妙的解答)

    概述 实现彩虹呼吸灯 题目就是这么简短,但这是目前我碰到的最有意思的一道题目,因为他有无数种解决方法,并且每一种都是那么高级或者巧妙,比如 可以利用3路不同初相的PWM调制信号驱动三颗RGB灯重叠呼吸 ...

  8. SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder”

    1.问题描述: 我的项目是tcServer,在运行的之后出现如下错误: SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBin ...

  9. For input string:"name"

    今天写项目的时候遇到了For input string:"name"这个错误,在控制台看报错信息好像是在说类型格式转换错误, 可是我并没有转换格式,这个name是我在jsp页面中使 ...

  10. Laravel 中使用 swoole 项目实战开发案例一 (建立 swoole 和前端通信)

    1 开发需要环境 工欲善其事,必先利其器.在正式开发之前我们检查好需要安装的拓展,不要开发中发现这些问题,打断思路影响我们的开发效率. 安装 swoole 拓展包 安装 redis 拓展包 安装 la ...