linux file description

Linux系统编程

文件IO

在对文件进行读写操作前,需要先打开该文件,内核为每个进程维护一个打开文件的列表,该表被称为文件表(file table)。该表由一些叫做文件描述符fdfile description)的非负整数进行索引。

clip_image001

每个进程按照惯例都会至少有三个打开的文件描述符:012,除非进程显式地关闭它们。文件描述符0是标准输入stdin,文件描述符1是标准输出stdout,文件描述符2是标准错误stderrC标准库提了预处理器宏:STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO宏,以取代对以上整数的直接引用。

clip_image002

      需要注意的是,文件描述符不仅仅用于普通文件的访问,也用于访问设备文件、管道、目录以及快速用户空间锁、FIFOs和套接字。遵循一切皆文件的理念,任何你能读写的东西都可以用文件描述符来访问。

关闭文件的副作用

clip_image003

关闭文件有一些副作用,当最后一个引用某文件的文件描述符关闭后,在内核中表示该文件的数据结构就被释放了,当它释放时,与文件关联的inode的内存拷贝被清除,如果没有什么连接到该inode,它可能会从内存中清除(也有可能保留在内存中,因为内核为了效率缓存一些inode,但也可能不需要)。如果文件已经从磁盘解除链接,但在解除前仍保持打开,它在被关闭且inode从内存中移除前就不会真的被删除。因而,对close的调用可能会使某个已经解除链接的文件最终从磁盘上被删除。

 

the meaning of postfix(.a,.la,.o,.so) and realize by programming

linux.a,.la,.o,.so文件的意义和编程实现

后缀含义

Linux下文件的类型是不依赖于其后缀名的,但一般来讲:

l  .o,是目标文件,相当于windows中的.obj文件

l  .so 为共享库,shared object,用于动态连接的,dll差不多

l  .a为静态库,是好多个.o合在一起,用于静态连接

l  .lalibtool自动生成的一些共享库,vi编辑查看,主要记录了一些配置信息。可以用如下命令查看.la文件的格式 $file .la .la: ASCII English text,所以可以用vi来查看其内容。

创建.a库文件和.o库文件:

$ gcc -c a.c

$ ar -rc a.a a.o

ar: creating a.a

动态库的编译

假定有三个.c文件:a.cb.cc.c,我们将这几个文件编译成一个动态库:libtest.so

$ gcc a.c b.c c.c -fPIC -shared -o libtest.so

动态库的链接

在上面,我们已经成功生成了一个自己的动态链接库libtest.so,下面我们通过一个程序来调用这个库里的函数。程序的源文件为:test.c

l test.c与动态库libtest.so链接生成执行文件test

$ gcc test.c -L. -ltest -o test

l 测试是否动态连接,如果列出libtest.so,那么应该是连接正常了

$ ldd test

l 执行test,可以看到它是如何调用动态库中的函数的。

编译参数解析

最主要的是GCC命令行的一个选项:

-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件

-fPIC表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的,所以动态库必须要有此选项

-L.:表示要连接的库在当前目录中

-ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称

LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。

当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。

注意

调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过“-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。

makefile里面怎么正确的编译和连接生成.so库文件,然后又是在其他程序的makefile里面如何编译和连接才能调用这个库文件的函数:我们需要告诉动态链接器、加载器ld.so在哪里才能找到这个共享库,可以设置环境变量把库的路径添加到库目录/lib/usr/libLD_LIBRARY_PATH=$(pwd),这种方法采用命令行方法不太方便,一种替代方法

LD_LIBRARY_PATH可以在/etc/profile还是 ~/.profile还是 ./bash_profile里设置,或者.bashrc里,改完后运行source /etc/profile . /etc/profile更好的办法是

添入/etc/ld.so.conf, 然后执行 /sbin/ldconfig

 

程序崩溃处理

程序崩溃处理

       有人说C语言是低级语言,这有一部分原因是因为应用程序的内存管理大部分需要由程序员来实现。虽然这种方法非常有用,但是也给程序员添加了很多的麻烦。

       也有人说,C语言是相对较小且容易学习的语言,然而,只有不考虑标准C语言库的典型实现时,C语言才比较小,这个库相当庞大,很多程序员认为C语言是易用语言,那是因为他们还没有遇到指针。

       一般而言,程序错误会导致下面两件事情的发生:

l  导致程序做一些程序员没有打算做的事情;

l  导致程序崩溃

 

相信很多调试过程序的兄弟都碰到过段错误即segmentation fault,则合格主要原因是试图在未经允许的情况下访问一个内存单元。硬件会感知这件事并执行对操作系统的跳转。

堆区域

       调用malloc函数分配的内存;

栈区域

       用来动态分配数据的空间,函数调用的数据(包括参数、局部变量和返回地址)都存储在栈上。

查看程序在Linux上的精确内存布局情况

       可以通过使用info proc mappings来详细查看该程序在Linux上的精确内存布局情况,例如:

clip_image002

此时我们还可以看到这个进程号为14455,所以我们还可以通过文件/proc/14455/maps来查看该信息。通过这些信息,我们有可能看到文本和数据区域,以及堆和栈。

分配页策略

       操作系统不会将不完整的页分配给程序,例如,如果要运行的程序总共大约有10000字节,如果完全加载,会占用3个内存页(一个页占4096个字节),它不会仅占用2.5个页,因为页是虚拟内存系统能够操作的最小内存单元,这是调试时要着重了解的情况,这也导致了程序的一些错误内存访问不会触发段错误,换言之,在调试会话期间,没有引起段错误并不能直接说明代码是没有问题的。

页的角色细节

       当程序执行时,它会连续访问程序中的各个区域,导致硬件按照以下几种情况所示处理页表:

l  每次程序使用全局变量时,需要具有对数据区域的读写访问权限;

l  每次程序访问局部变量时,程序会访问栈,需要对栈区域具有读写访问权限;

l  每次程序进入或离开函数时,对该栈进行一次或多次访问,需要对栈区具有读写访问权限;

l  每次程序访问通过调用malloc或者new创建的存储器时,都会发生堆访问,也需要读写访问权限;

l  程序执行的每个机器指令是从文本区域取出的,因此需要具有读和执行文件;

信号

       这里需要注意的是,进程抛出的信号,实际上没有任何内容发送给进程。所发生的事情只不过是操作系统将信号记录到进程表中,以便下次进程接收信号时得到CPU上的时间片,执行恰当的信号处理程序。

自定义信号的复杂性

       使用GDB/DDD/Eclipse调试时,自定义信号处理程序可能会使程序变得复杂,无论是直接使用还是通过DDD GUI,每当发出任何信号时,GDB都会停止进程,所以,有可能意味着GDB会因为与调试无关的工作而频繁的停止,此时可以使用handle命令告诉GDB在某些信号发生时不要停止。

总线错误的原因

l  访问不存在的物理地址;

l  在很多架构上,要求访问32位量的机器指令要求字对齐,而导致视图在奇数号地址上访问具有4字节的数的指针错误可能引起总线错误。

总线错误是处理器层的异常,导致在Unix系统上发出SIGBUS信号,默认情况下,SIGBUS会导致转储内存并终止。

核心文件

       有些信号表示让某个进程继续是不妥当的,甚至是不可能的,在这些情况中,默认动作是提前终止进程,并编写一个名为核心文件core file文件,俗称转储核心

       核心文件包含程序崩溃时对程序状态的详细描述:栈的内容、CPU寄存器的内容、程序的静态分配变量的值。

       我们可以通过file命令来查看文件的详细信息。

为什么需要核心文件

l  只有在运行了一段长时间后才发生段错误,所以在调试器中无法重新创建该错误;

l  程序的行为取决于随机的环境事件,因此再次运行程序可能不会再现段错误;

l  当新手用户运行程序时发生的段错误,需要发送核心文件给开发人员。

重载功能

       GDB注意到重新编译了程序后,它会自动加载新的可执行文件,因此不需要退出和重启GDB

调试设计的内容

l  确认原则;

l  使用核心文件进行崩溃进程的“死后”分析;

l  纠正、编译并重新运行程序后,甚至不需要退出GDB

l  Printf()风格调试的不足之处;

l  利用你的智慧,这是无可替代的;

l  如果你过去使用printf风格调试的,就会发现使用printf跟踪这些程序错误中的部分错误原来有多难,虽然在调试中使用printf诊断代码有一定的好处,但是作为一种通用目的的工具,它远远不足以用来跟踪实际代码中发生的大部分程序错误。