关于Linux的系统调用

【这里只研究 x86 平台下的】
1. 相关文件:
系统调用号的定义:
arch/x86/include/asm/unistd_32.h

这里对每个系统调用都分配了一个系统调用号。

系统调用表的定义:
arch/x86/kernel/syscall_table_32.S

这里是用汇编语言定义的系统调用表,系统启动时会加载到指定的内存区域

那么,这些sys_* 系统调用例程怎么定义的呢? 在某个版本以前,我们可以直接通过sys_* 找到该函数的定义,但是,在linux-3.1.1中,我们只能看到使用该函数的地方,却找不到定义该函数的地方了,因为可能通过宏来定义了,如: SYSCALL_DEFINE0, SYSCALL_DEFINE1,SYSCALL_DEFINE2,…
参考文件 include/linux/syscalls.h:

当然,也可能没有通过宏来定义,我们不妨还按照sys_ 来搜索。

让我们来找找sys_fork 吧:
grep SYSCALL_DEFINE -r * | grep fork
没有结果, 在试试sys_fork
grep sys_fork -r *
结果太多了,我只关心x86平台下的实现,如下:
grep sys_fork -r * | grep x86
大概在文件:./arch/x86/kernel/process.casm/syscalls.h 中

再看看./arch/x86/kernel/process.c :

我们知道,系统调用也是通过一个系统中断来实现的,linux上,所有的系统调用都是通过一个中断号为 0x80的中断来实现的,参考文件:
arch/x86/include/asm/irq_vectors.h

那么,中断向量表又是在什么时候初始化的呢?
arch/x86/kernel/traps.c 中初始化了中断向量表,里面也指定了 0x80 的中断处理例程是 system_call

那么, system_call 又是怎么定义的呢?
arch/x86/kernel/entry_32.S

这里我们还发现系统调用被跟踪的逻辑,就是 syscall_trace_entry, 同样出现在文件: arch/x86/kernel/entry_32.S中;
ptrace 函数就是利用这个功能来实现的。

那么, syscall_trace_enter 的实现又是在哪里的呢?
arch/x86/kernel/ptrace.c

ptrace是不是很强大? 记住: ptrace本身可不是系统调用,如果是的话就死循环了。

问题: ptrace.c 中没有定义ptrace这个函数,但是 man ptrace 时,却又如下定义:
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

那么这个函数又是怎么来的呢?

====================================================================

现在,我要执行一个fork系统调用时,是怎么通过fork符号来找到系统调用表中的指定的例程的呢???????????????

下面的就不要看了,只是猜测。。。。。。。。。。。。。。。。。。

系统调用都到这里来执行:
arch/x86/kernel/sys_i386_32.c

对于较新的cpu来讲,系统调用是通过: ./vdso/vdso32/sysenter.S 中的__kernel_vsyscall来实现的,其中使用了一个新的cpu指令: sysenter

关于Linux共享库的一点儿知识

1. 关于动态链接的文件依赖的共享库:
链接时使用-l指定的库文件都会记录到elf文件里面的,程序加载前的动态链接一定会加载的,不管你的程序是否会用到;如:
======= phpor.c ============

  1. #include <stdlib.h>
  2. void phpor_hello() {
  3.         printf(“hello\n”);
  4. }

======= phpor_test.c =============

  1. //void phpor_hello();
  2. int main(int ac, char **av) {
  3. //  phpor_hello();
  4.     printf(“no dependent \n”);
  5.     return 0;
  6. }

———————————-

# gcc -shared -o libphpor.so phpor.c
# gcc -o phpor_test -l phpor -L. phpor_test.c
# LD_LIBRARY_PATH=. ./phpor_test
no dependent
# LD_LIBRARY_PATH=. ldd ./phpor_test
libphpor.so => ./libphpor.so (0x00e4f000)
libc.so.6 => /lib/tls/libc.so.6 (0x00865000)
/lib/ld-linux.so.2 (0x00846000)

你可以把libphpor.so 删掉,执行的时候就报错了

2. 关于共享库的版本号:
我创建了一个so文件 libphpor.so.1 ,现在我要编译一个phpor_test的文件,该文件依赖libphpor.so.1,于是有如下命令:
gcc -o phpor_test -l phpor -L. phpor_test.c
我的libphpor.so.1 是在当前目录下的,所以 -L.
如果我不创建软连接 libphpor.so => libphpor.so.1 的话,则是无法连接的。 为什么不能直接使用 libphpor.so.1 呢? 关键是:
# ldd /bin/ls
librt.so.1 => /lib/tls/librt.so.1 (0x00ce9000)
libacl.so.1 => /lib/libacl.so.1 (0x00f7f000)
libselinux.so.1 => /lib/libselinux.so.1 (0x00a22000)
libc.so.6 => /lib/tls/libc.so.6 (0x00865000)
libpthread.so.0 => /lib/tls/libpthread.so.0 (0x009f5000)
/lib/ld-linux.so.2 (0x00846000)
libattr.so.1 => /lib/libattr.so.1 (0x00502000)

为什么ls这个命令就能依赖一个带有主版本号的库,而我就不能依赖指定的主版本号?

我只好创建一个软连接 libphpor.so => libphpor.so.1 ,因为该so文件没有在LD_LIBRARY_PATH 下,所以,这里测试时临时指定一下:
LD_LIBRARY_PATH=. ./phpor_test

3. 关于共享库、静态库和动态链接、静态链接的概念
共享库、静态库

强调的是:

我能否其它程序动态加载

特点:

  1. 静态(不是静态链接的可执行程序)可能(也可能不)依赖别的未定义的符号,但是,自己不知道(也不会说明)这些符号在那里可以找到,即: 不描述依赖关系
  2. 共享库可能(也可能不)依赖别的未定义的符号,但是,如果有未定义符号,则自己会说明需要动态加载哪些共享库,即:描述依赖关系
  3. 其实,静态库实际是没有经过链接的,只是一些目标文件的打包;共享库是经过了链接这个步骤了的

动态链接、静态链接:

强调的是:

我(就是链接的结果文件,可能是一个可执行文件,也可能是一个共享库so文件,注意,so文件有的也可以直接执行,但一定不是静态库文件)在运行的时候,是否依赖别的动态库文件。

特点:

  1. 动态链接的文件一定依赖其他的so文件,至少要依赖 ld.so
  2. 静态链接的文件一定不依赖其他的so文件

库类型与链接类型之间的关系:

  1. 共享库也可以是静态链接的,如:
    # ldd /lib/ld-linux.so.2
    statically linked
  2. 可以使用共享库来静态链接生成一个静态的可执行程序,如:
    LD_LIBRARY_PATH=. gcc -o phpor_test -static -l phpor -L. phpor_test.c
  3. 你也可以使用静态库来动态链接生成一个动态的可执行程序, 如:
    # gcc -o phpor_test phpor_test.c ./libphpor.a
    # ./phpor_test
    hello
    # ldd phpor_test
    libc.so.6 => /lib/tls/libc.so.6 (0x00b9a000)
    /lib/ld-linux.so.2 (0x00b7b000)

4. 我们说,共享库也可以是静态链接的,下面我们做一个实验,把上面的phpor.c 编译成一个静态链接的共享库文件:
因为linux几乎所有程序都依赖libc.so, 现在要静态链接,就需要指定需要的.a 文件 libc.a, 在/usr/lib下; 一般默认是动态链接的,如果要静态连接,就需要使用 -static 选项指定,命令如下:
gcc  -shared  -static -o libphpor.so.2 /usr/lib/libc.a  phpor.c
但是,这样编译的结果还是动态链接的,为什么? -static 没有起作用吗?
使用如下命令:
# gcc  -shared  -nodefaultlibs -o libphpor.so.2 /usr/lib/libc.a  phpor.c
# ldd libphpor.so.2
statically linked

当然,您可以同时使用 -nodefaultlibs和 -static ; 不明白的是: 难道static不是用来屏蔽其它的动态库文件的吗?

先生成目标文件,自己使用ld命令来做静态连接:
# gcc -o phpor.o -c phpor.c
# ld -shared -dn -o libphpor.so.3 phpor.o
# ldd libphpor.so.3
statically linked
# ln -s  libphpor.so.3 libphpor.so
# LD_LIBRARY_PATH=. ./phpor_test
hello
又有问题了,为什么我没有指定/usr/lib/libc.a , 这里却能链接完成,而且还能用了呢?难道ld帮我做了?

 

动态库不仅能节省磁盘空间,也能节省内存空间。

 

什么是导出(export)环境变量

1.  执行如下命令:
aVar=theValue
echo $aVar
不会输出theValue

2. 执行如下命令:
export aVar=theValue
echo $aVar
输出: theValue
说明:
如果没有export, 则,只作用于当前的命令; 如果export了,则相当于修改了当前shell的环境

3. 比较如下两个命令

第一个命令中的aVar是当前shell中的环境变量,因为没有使用export,所以只为空;
第二个命令(注意:使用的是单引号)中的$aVar是eval进程中的环境变量,而前面没有使用export,所以,只作用于eval进程,所以aVar就有值了

eval 与 env

一般用法:
eval:
语法

    

        

            

        

    

[test @test test]# eval variable
            例题:
            [test @test test]# days=365
            [test @test test]# year=days
            [test @test test]# echo \$$year
            $days  <==第一个 $ 被 \ 改变成为一般字符,而 \$ 后面接的 $year 就成为 days 啦!
            [test @test test]# eval echo \$$year
            365
            加上 eval 之后, \$$year 变成的 $days 的『变量内容』会显现出来喔!

这个指令也是颇有趣的!他主要是用来做为变量的『迭代』用的!以上面的例子来看,起先, \$$year 会变成为 $days ,而这个 $days 其实是一般字符喔!并不是变数!不过,加上了 eval 之后,这个字符串就会被变成变量内容咯!所以说, eval 是用来做为『二次迭代』的功能的!

env
显示目前系统中主要的预设变量内容
语法

    

        

            

        

    

[test @test test]# env
            ENV=/root/.bashrc             <==使用者自订环境变量的设定档案
            HISTSIZE=1000                 <==目前的指令记忆数量
            HOME=/home/test               <==登入者的家目录
            HOSTNAME=test.adsldns.org  <==这部主机的主机名称
            HOSTTYPE=i386              <==这部主机的硬件等级大致状态(i386, i686..)
            INPUTRC=/etc/inputrc          <==一些 shell 加载的数据文件设定处
            LANGUAGE=C                    <==预设语系的资料
            LANG=zh_TW.Big5 
            与 LANGUAGE 类似,这个则是各个 linux distribution 常用的预设语系变量,由于我的 Mandrake 使用中文安装,所以预设语系是中文,亦即 zh_TW.Big5 ,如果我要修改这个变量,可以到 /etc/sysconfig/i18n 去修改!底下的 LC_xxx 均是与预设的表示语系有关的变量,其中比较有趣的是 LC_TIME ,如果在文字接口下,最好将 LC_TIME 改成美规日期的显示方式,才不会有乱码!
            LC_COLLATE=zh_TW.Big5
            LC_CTYPE=zh_TW.Big5
            LC_MESSAGES=zh_TW.Big5
            LC_MONETARY=zh_TW.Big5
            LC_NUMERIC=zh_TW.Big5
            LC_TIME=en
            LESSOPEN=|/usr/bin/lesspipe.sh %s <==用来设定 less 使用的一支 script 档案
            LOGNAME=test                  <==登入者的账号            

MACHTYPE=i586-mandrake-linux-gnu 
            主机的硬件配备等级 i586 为 P MMX 等级,至于 K7 及 PIII 之后的,就是 i686 等级啰!

            

MAIL=/var/spool/mail/test     <==登入者的邮件预设放置地点
            OSTYPE=linux-gnu              <==操作系统的形式(linux-gnu)
            PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/test/bin
            PWD=/home/test                <==目前登入者所在的目录(当下的目录)
            SHELL=/bin/bash               <==登入者使用的 shell 类型
            USER=test                  <==目前这个登入者的使用者名称

            

env environment 的简写,所以说,这个指令主要在将目前系统中的主要变量读出来!但是,不是说我们还可以自订变量吗?因此,除了 env 这个读取环境变量的指令之外,还有一个可以将目前系统中所有的变量数据都读出来的指令,称为 set !set 除了会将上面的数据都给他读出来之外,还会有额外的这些信息也一起读入(通常都与使用者的设定有关!)

其它用法:
——————————-
有php文件a.php:
<?php
echo getenv("a");
?>
则:
eval a=b php a.php
等价于
env a=b php a.php

即: 二者都可以设置环境变量

netstat、SYN_RECV 和 tcp中的坚持定时器

netstat是一个查看网络状况的非常给力的工具,这里介绍一个不常用的选项:
-o, –timers
Include information related to networking timers.

如果添加-o选项,结果为:

我们发现,多出了一些信息,那么多出来的这些信息有什么用吗?man了一下netstat,知道这里是定时器,此外也并没有什么有用的说明。

我想,既然是定时器,应该与 SYN_RECV 状态持续的时间有关系,怎么才能够证明呢?
首先,先祭出一个非常有用的工具: nc ; nc是个好东西,可以做server也能做client; 更加凑巧的是nc做server时,listen的backlog是为1的:

backlog为1 将非常有利于我们做这个实验; 关于backlog的说明:
backlog通常称为“积压值”;“积压值”说明的是TCP监听的端点已被TCP接受(established的状态)而等待应用层接受(使用-p参数时看到的pid列为’-‘)的最大连接数。
下面你讲看到,虽然nc设置的backlog为1,但是,排队的连接数却不是1,而是2;一般来讲:
积压值和最大排队的连接数有如下关系:

扯远了,赶快回来…
1. 使用nc做一个server
nc -l 10.71.6.21 12345
2. 从另一台机器上去连接该server
nc 10.71.6.21  12345 &  // 写四遍就行了

在server端可以使用netstat来观察如下:

至于括号中的数字,我们再观察一个图:

我每次查看那个不能被立即接受的连接,括号中的数字都是变的,而且和等待的时间长短是有关系的;第一列数字是从某个数字开始递减的,递减到0,则第二列数字加1;然后第一列数字继续从一个新的数值开始递减,就这样一直等下去,直到客户端超时。新的数字的取值按照指数避让算法计算出来的。
关于定时器的知识可以参考《tcp/ip详解》

关于SYN_RECV 状态:
当server收到syn后,server端的连接的状态变为SYN_RECV,这时候:
1. 可能server还没有回复了syn-ack
2. 也可能server已经回复了syn-ack
3. 还可能server不仅回复了syn-ack,而且还收到了client的ack(这种情况属于不符合规范的实现)

情况1: 如果状态持续时间太长,需要查看 netstat -sn| grep overflow 是否变化
情况2: 一般可能是client太远了
情况3: 应用进程可能忙不过来了,可以查看 netstat -anop | grep -i established | wc -l ,可能太多了

一般来讲,如果netstat -anop | grep -i established| awk ‘{if($7 == "-") print $0}’| wc -l 不是太大,就没有什么问题