• kernel笔记——库文件与系统调用


    库文件

    先从我们熟悉的c库入手,理解系统调用(system call)。c代码中调用printf函数,经历了以下调用过程:

     

    最终输出的功能由内核中write调用完成,c库封装了系统调用。

    对于以下hello world程序:

    #include
    int main()
    {
      printf("Hello world.
    ");
      return 0;
    }

    我们可以使用ldd查看程序依赖的库文件:

    linux # ldd hello
        linux-vdso.so.1 => (0x00007fff89fe2000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fd142094000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fd1423f2000)

    输出结果中显示了hello程序依赖的动态库文件,其中linux-vdso.so.1指向进程虚拟内存地址,是一个虚拟的库文件,在每个程序的虚拟内存都存在,其将内核态的调用映射到用户地址空间中,使得调用开销更小。

    有些时候,在我们编译程序时,会出现找不到某某lib的提示,又或者ldd查询到某库链接指示”not found”,这是因为ldd在/etc/ld.so.cache中不存在相应库文件的查找路径(使用strace跟踪ldd命令可以看到ld.so.cache文件被读取)。

    要解决找不到某库的问题,我们可以将库文件路径加到用户的LIBRARY_PATH环境变量中,也可以添加到全局的/etc/ld.so.conf配置文件中,添加完后以root用户执行ldconfig,以更新/etc/ld.so.cache缓存文件。

    使用rpm命令可以查到动态库属于哪个rpm包:

    linux # rpm -qf /lib64/libc.so.6
    glibc-2.11.1-0.17.4

     

    反过来,对某一rpm包,我们可以查其包含的内容:

    linux # rpm -ql glibc-2.11.1-0.17.4
    /etc/bindresvport.blacklist
    /etc/default/nss
    /etc/gai.conf
    /etc/ld.so.cache
    /etc/ld.so.conf
    ……

     

    devel包中包含了c库函数的头文件,而普通包中不包含头文件,可以使用rpm查询对比开发包和普通包:

    rpm -ql glibc-2.4-31.77.88.4
    rpm -ql glibc-devel-2.4-31.77.88.4

    系统调用

    每一个系统调用对应一个系统调用号(system call number),使用系统调用的过程就是将系统调用号和参数传递给内核。

    使用objdump,可以对库文件进行反汇编,以下对/lib64/libc.so.6进行反汇编,并查看getpid函数相应的部分汇编代码:

    00000000000933e0 <__getpid>:
    ……
    933fa: 00
    933fb: 85 c0           test %eax,%eax
    933fd: 75 f0           jne 933ef <__getpid+0xf>
    933ff: b8 27 00 00 00  mov $0x27,%eax
    93404: 0f 05           syscall
    93406: 85 d2           test %edx,%edx
    ……

    在以上输出中,mov指令将系统调用号0x27放入eax寄存器中,0x27作为syscall的参数,syscall完成调用getpid的工作。

    系统调用与系统调用号对应关系在include/asm/unistd.h中定义,我们可以查到getpid相应的定义语句:

    #define __NR_getpid 39
    __SYSCALL(__NR_getpid, sys_getpid)

    unistd.h定义了POSIX标准提供的系统调用,所有符合POSIX标准的Unix系统均提供该头文件。

    我们可以直接传递系统调用号给syscall函数,完成系统调用,以下程序说明了如何使用syscall直接完成getpid系统调用:

    #define _GNU_SOURCE
    #include
    #include
    #include
    #include
    int main(int argc, char *argv[])
    {
    pid_t tid;
    tid = syscall(SYS_gettid);
    printf("%d
    ", tid);
    }

    因而总结来说,使用系统调用的方式有两种:

    1. c库中封装了系统调用,通过c库间接调用
    2. 传递系统调用号,通过syscall直接调用

    第2种方式存在的意义在于,当kernel提供了新的系统调用,而c库又没有更新时,可以使用syscall调用新的系统调用。

    Reference: Chapter 5 - System Calls, Linux kernel development.3rd.Edition

  • 相关阅读:
    全网最通透的“闭包”认知 -超越语言
    C# 8.0 宝藏好物 Async streams
    Ingress-nginx工作原理和实践
    鹅厂二面,nginx回忆录
    .NET gRPC 核心功能初体验,附Demo源码
    python工业互联网应用实战8—django-simpleui
    python工业互联网应用实战7—业务层
    python工业互联网应用实战6—任务分解
    python工业互联网应用实战5—Django Admin 编辑界面和操作
    python工业互联网应用实战4—Django Admin列表
  • 原文地址:https://www.cnblogs.com/felixzh/p/9047727.html
Copyright © 2020-2023  润新知