8139too的工具ethtool的源码分析-ioctl系统调用的分析

8139too的工具ethtool的源码分析-ioctl系统调用的分析
8139too的工具ethtool的源码分析-ioctl系统调用的分析

8139too的工具ethtool源码分析

ethtool is a small utility for examining and tuning your ethernet-based network interface.由于任何一个应用程序都要有一个main()的入口函数,既然ethtool作为一个在Linux平台下的应用程序,当然也有ethtool自己

的main()入口函数。

分析的ethtool的源码为:ethtool-2.6.36

ethtool的入口main()函数为 Ethtool.c:

下面我以:ethtool -i eth1 ;这条命令为例来说明调用函数的流程,最为关键的是:ethtool这个用户态的应用程序是如何调用内核态中rtl8139too.c中与该命令功能相关的函数的。

当输入ethtool -i eth1这条命令时,将会调用:parse_cmdline()

查看源码可以知道,args是源码中定义的一个结构体数组:

对应ethtool -i eth1这条命令,args[]的值为:

则:.str="-i"; .lng="--driver"; .Mode=MODE_GDRV; .help="Show driver information";

则代码中的mode= MODE_GDRV; devname = argp[i]="-i".

以上是当switch(i)中i=1的情况,当i=2时:

由于在case 1中mode= MODE_GDRV则符合case 2中的情况之一,执行:

则由case 1中的devname = argp[i]="-i"变成为:

devname=argp[2]="eth1"。因为在例子中的这条命令的argc=3,则这个for(i=1;i

此时:mode= MODE_GDRV;devname=argp[2]="eth1".

在此说明一下:devname 和mode 都是全局变量。

得到上面的结果后,从parse_cmdline()返回,进入doit() (Ethtool.c中):

首先看看ifreq的定义:

在doit()函数中,完成初始化ifr 结构体,然后:

跳转到do_gdrv()函数中,其中的参数的ifr.ifr_name="eth1",fd为创建的socket句柄。

Do_gdrv()函数在ethtool.c中,如下图:

在这个函数中,我们看到drvinfo这个结构体,这个结构体是用来存储eth1的信息的,并且将这个存储结构体放在了ifr-->ifr_data=(caddr_t)&drvinfo. 接下来就是最关键的地方了,在err=send_ioctl()中,将socket句柄fd, 和拥有

ifr_name="eth1"和ifr_data的结构体ifr作为参数传给了send_ioctl(),这个函

数在ethtool.c中,我们看看send_ioctl():

可以看到其实是调用了ioctl(),并将:

也作为参数传给了ioctl(), 0x8946用于标示这是一个ethtool interface的接口标记字符串。

ioctl()是个系统调用函数。由于ethtool应用程序最终会调用rtl8139too.c 网卡驱动程序中的函数,对此,(Qemu+GDB)我在rtl8139too.c的

rtl8139_get_settings()设置断点,当我在系统的终端输入ethtool eth0的命令时,内核会在该函数断点处停下,查看此时的堆栈的信息,为如下图所示:

从这个图中,我们可以看到最先是从sys_ioctl()函数开始执行的。在《深入理解linux内核》中的系统调用章节有讲到:

一个系统调用分为用户态和内核态两部分:

从上面的这个图可以知道,在内核中首先从sys_ioctl()开始执行,在用户态的ethtool应用程序调用ioctl(),并且包含sys/ioctl.h。那么问题就来了,从用户态的应用程序中的ioctl(),是怎么转到执行内核态的sys_ioctl()函数?

关键就在于在用户程序和内核之前的libc库函数。Sys/ioctl.h是glibc库中的头文件。为此,我查看了glibc库的源代码。

下面是glibc库的源代码中,有这样的代码,代码所在的路径为:glibc-

2.11/sysdeps/unix/sysv/linux/powerpc/ioctl.c

具体的过程,我没有弄很清楚。但是,从上面的代码可以大概明白,

result=INLINE_SYSCALL()就是这个连接的桥梁。

通过这个桥梁,最终导致了内核sys_ioctl()的调用。

在这里,顺便解释一下:

weak_alias();

weak_alias(name1,name2)

为标号name1定义一个弱化名name2。仅当name2没有在任何地方定义时,连接器就会用name1解析name2相关的符号。在文件中定义的

标号name1也会同样处理。

对于socket貌似有weak_alias (__socket, socket)

在glibc的sysdeps目录下面socket.S

(目录为:glibc-2.11/sysdeps/unix/sysv/linux/i386/socket.S

),有如下代码。

.globl __socket

ENTRY (__socket) ,

在这个socket.S的程序中,将调用号压入eax,参数压入其他一串寄存器中(看有多少个参数了),然后会执行system_call程序(系统初始化时候设置的set_system_gate(SYSCALL_VECTOR, &system_call);)接下来一系列调整内核栈的操作以及环境的设置后,就会用eax表示的系统调用号为索引,在系统调用跳转表中寻找系统调用的处理函数了,即sys_socket())。

那么将调用号参数压入寄存器eax后,还需要向系统内核发送一个中断信号,

那这是怎么做的呢?

首先,来看看INLINE_SYSCALL实现:

在glibc-2.11/sysdeps/unix/sysv/linux/i386/sysdep.h 中,

关系到INTERNAL_SYSCALL,那它的实现是:

仔细分析上面的代码:

在ioctl()的系统调用中,需要向内核发送一个系统调用的中断信号。在此,发送这个信号的方法有两种:

第一种方法:

当I386_USE_SYSENTER=true时,采用的是:

第二种方法:

当I386_USE_SYSENTER=false时,采用的是传统的方法,即int 0x80:

所以,有时我们在系统调用时,找不到int 0x80这样的汇编代码。当确实没有找到时,可能采用的是第一种方法。

关键"call *%%gs:%P2\n\t"是怎么做,达到和int 0x80一样的效果的呢?

movl %1, %%eax\n\t"是将下面第一个参数:__NR_##name即该系统调用号放到eax中。但是后面是怎么实现的,没有找到相关的资料。

相关主题
相关文档
最新文档