构建调试Linux内核网络代码的环境MenuOS系统

1:实验环境选择

我选择的是实验楼平台,在 LinuxKernel 目录已经构建好了基于 3.18.6 的内核环境,可以使用实验楼的虚拟机打开 Xfce 终端(Terminal), 运行 MenuOS 系统。

2:启动内核

打开终端键入以下命令:

$ cd ~/LinuxKernel/
$ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img

内核启动:

构建调试Linux内核网络代码的环境MenuOS系统_第1张图片

 

 

 使用跟踪分析 ~/Linux 内核的启动过程的 -s 和 -S 选项启动 MenuOS 系统。

$ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

关于 -s 和 -S 选项的说明:

  • -S freeze CPU at startup (use ’c’ to start execution)
  • -s shorthand for -gdb tcp::1234 若不想使用 1234 端口,则可以使用 -gdb tcp:xxxx 来取代 -s 选项

 

3:执行gdb

打开一个 Xfce 终端(Terminal),执行 gdb

# 打开 GDB 调试器
$ gdb

# 在 GDB 中输入以下命令:

# 在 gdb 界面中 targe remote 之前加载符号表
(gdb)file linux-3.18.6/vmlinux 

# 建立 gdb 和 gdbserver 之间的连接
(gdb)target remote:1234

# 断点的设置可以在target remote之前,也可以在之后
(gdb)break start_kernel 

# 按 c 让qemu上的Linux继续运行
(gdb)c   

构建调试Linux内核网络代码的环境MenuOS系统_第2张图片

 

gdc的继续执行命令会执行到设置的断点

 

构建调试Linux内核网络代码的环境MenuOS系统_第3张图片

 

4:将网络通信程序的服务端集成到 MenuOS 系统中

接下来我们需要将 C/S 方式的网络通信程序的服务端集成到 MenuOS 系统中,成为 MenuOS 系统的命令 replyhi。

我们 git clone 克隆一个 linuxnet.git,进入 lab2 目录执行 make 可以将我们集成好的代码 copy 到 menu 项目中。

然后进入 menu,我们写了一个脚本 rootfs,运行 make rootfs,脚本就可以帮助我们自动编译、自动生成根文件系统,还会帮我们运行起来 MenuOS 系统。

详细命令如下:

$ cd ~/LinuxKernel  
$ git clone https://github.com/mengning/linuxnet.git
$ cd linuxnet/lab2
$ make
$ cd ../../menu/
$ make rootfs

构建调试Linux内核网络代码的环境MenuOS系统_第4张图片

 

尝试输入replyhi命令

构建调试Linux内核网络代码的环境MenuOS系统_第5张图片

 

 可以正常通信

这里简单介绍一下socket接口函数的内核处理函数sys_socket

摘录其中的关键代码如下:

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

    retval = sock_create(family, type, protocol, &sock);
}

socket接口函数主要作用是建立socket套接字描述符,Unix-like系统非常成功的设计是将一切都抽象为文件,socket套接字也是一种特殊的文件,sock_create内部就是使用文件系统中的数据结构inode为socket套接字分配了文件描述符。socket套接字与普通的文件在内部存储结构上是一致的,甚至文件描述符和套接字描述符是通用的,但是套接字和文件还是特殊之处,因此定义了结构体struct socket,struct socket的结构体定义见/linux-3.18.6/include/linux/net.h#105,具体代码摘录如下:

struct socket {
    socket_state        state;

    kmemcheck_bitfield_begin(type);
    short            type;
    kmemcheck_bitfield_end(type);

    unsigned long        flags;

    struct socket_wq __rcu    *wq;

    struct file        *file;
    struct sock        *sk;
    const struct proto_ops    *ops;
};

bind()函数

正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数的三个参数分别为:

  • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
  • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是: 
    struct sockaddr_in {
        sa_family_t    sin_family; /* address family: AF_INET */
        in_port_t      sin_port;   /* port in network byte order */
        struct in_addr sin_addr;   /* internet address */
    };
    
    /* Internet address. */
    struct in_addr {
        uint32_t       s_addr;     /* address in network byte order */
    };
    ipv6对应的是: 
    struct sockaddr_in6 { 
        sa_family_t     sin6_family;   /* AF_INET6 */ 
        in_port_t       sin6_port;     /* port number */ 
        uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
        struct in6_addr sin6_addr;     /* IPv6 address */ 
        uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
    };
    
    struct in6_addr { 
        unsigned char   s6_addr[16];   /* IPv6 address */ 
    };
    Unix域对应的是: 
    #define UNIX_PATH_MAX    108
    
    struct sockaddr_un { 
        sa_family_t sun_family;               /* AF_UNIX */ 
        char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
    };
  • addrlen:对应的是地址的长度。

通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

listen()和connect()函数

如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

5:gdb调试

 接下里可以使用gdb对内核进行调试,如list命令:

 构建调试Linux内核网络代码的环境MenuOS系统_第6张图片

 

 参考资料:https://github.com/mengning/net/blob/master/doc/socketSourceCode.md

 

你可能感兴趣的:(构建调试Linux内核网络代码的环境MenuOS系统)