第0章Linux环境到内核基础知识

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. printf("hello world\n");
  5. return 0;
  6. }

gcc -g -wall helloworld.c -o hello_world 生成可执行文件,其过程 涉及预处理,编译,汇编,链接等多个步骤

预处理:用于处理预处理命令,上面helloworld代码的预处理就是#include,该头文件所有源码将在第一行展开,可使用 gcc -E helloworld.c > helloworld.i ,生成预处理文件。理解了预处理,在出现一些常见的错误时,才能明白其中的原因。比如,为什么不能在头文件中定义全局变量?这是因为定义全局变量的代码会存在于所有以#include包含该头文件的文件中,也就是说所有的这些文件,都会定义一个同样的全局变量,这样就不可避免地造成了冲突

编译环节指的是对源代码进行语法分析,并优化产生汇编代码(而不是二进制代码)

gcc -S helloworld.c -o helloworld.s

接下来汇编阶段,就是将汇编代码翻译成可执行的指令 gcc -c helloworld.c -o hellowrold.o

链接阶段是生成可执行文件的最后一个步骤,其工作是将各个目标文件--包括库文件,链接生成一可执行文件。这个过程中,涉及的概念比较多,比如地址和空间分配,符号解析,重定位等在Linux环境下由GNU的连接器ld完成的

gcc -g -Wall -v helloworld.c -o helloworld

--------------------------------------------------程序的构成----------------------------------

Linux下可执行文件的格式为elf格式,下面使用readelf查看helloworld格式

  1. ELF Header:
  2. Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  3. Class: ELF64
  4. Data: 2‘s complement, little endian
  5. Version: 1 (current)
  6. OS/ABI: UNIX - System V
  7. ABI Version: 0
  8. Type: EXEC (Executable file)
  9. Machine: Advanced Micro Devices X86-64
  10. Version: 0x1
  11. Entry point address: 0x4003c0
  12. Start of program headers: 64 (bytes into file)
  13. Start of section headers: 2560 (bytes into file)
  14. Flags: 0x0
  15. Size of this header: 64 (bytes)
  16. Size of program headers: 56 (bytes)
  17. Number of program headers: 8
  18. Size of section headers: 64 (bytes)
  19. Number of section headers: 29
  20. Section header string table index: 26
  21. Section Headers:
  22. [Nr] Name Type Address Offset
  23. Size EntSize Flags Link Info Align
  24. [ 0] NULL 0000000000000000 00000000
  25. 0000000000000000 0000000000000000 0 0 0
  26. [ 1] .interp PROGBITS 0000000000400200 00000200
  27. 000000000000001c 0000000000000000 A 0 0 1
  28. [ 2] .note.ABI-tag NOTE 000000000040021c 0000021c
  29. 0000000000000020 0000000000000000 A 0 0 4
  30. [ 3] .hash HASH 0000000000400240 00000240
  31. 0000000000000024 0000000000000004 A 4 0 8
  32. [ 4] .dynsym DYNSYM 0000000000400268 00000268
  33. 0000000000000060 0000000000000018 A 5 1 8
  34. [ 5] .dynstr STRTAB 00000000004002c8 000002c8
  35. 000000000000003d 0000000000000000 A 0 0 1
  36. [ 6] .gnu.version VERSYM 0000000000400306 00000306
  37. 0000000000000008 0000000000000002 A 4 0 2
  38. [ 7] .gnu.version_r VERNEED 0000000000400310 00000310
  39. 0000000000000020 0000000000000000 A 5 1 8
  40. [ 8] .rela.dyn RELA 0000000000400330 00000330
  41. 0000000000000018 0000000000000018 A 4 0 8
  42. [ 9] .rela.plt RELA 0000000000400348 00000348
  43. 0000000000000030 0000000000000018 A 4 11 8
  44. [10] .init PROGBITS 0000000000400378 00000378
  45. 0000000000000018 0000000000000000 AX 0 0 4
  46. [11] .plt PROGBITS 0000000000400390 00000390
  47. 0000000000000030 0000000000000010 AX 0 0 4
  48. [12] .text PROGBITS 00000000004003c0 000003c0
  49. 0000000000000258 0000000000000000 AX 0 0 16
  50. [13] .fini PROGBITS 0000000000400618 00000618
  51. 000000000000000e 0000000000000000 AX 0 0 4
  52. [14] .rodata PROGBITS 0000000000400628 00000628
  53. 0000000000000010 0000000000000000 A 0 0 4
  54. [15] .eh_frame_hdr PROGBITS 0000000000400638 00000638
  55. 0000000000000024 0000000000000000 A 0 0 4
  56. [16] .eh_frame PROGBITS 0000000000400660 00000660
  57. 000000000000007c 0000000000000000 A 0 0 8
  58. [17] .ctors PROGBITS 00000000006006e0 000006e0
  59. 0000000000000010 0000000000000000 WA 0 0 8
  60. [18] .dtors PROGBITS 00000000006006f0 000006f0
  61. 0000000000000010 0000000000000000 WA 0 0 8
  62. [19] .jcr PROGBITS 0000000000600700 00000700
  63. 0000000000000008 0000000000000000 WA 0 0 8
  64. [20] .dynamic DYNAMIC 0000000000600708 00000708
  65. 0000000000000190 0000000000000010 WA 5 0 8
  66. [21] .got PROGBITS 0000000000600898 00000898
  67. 0000000000000008 0000000000000008 WA 0 0 8
  68. [22] .got.plt PROGBITS 00000000006008a0 000008a0
  69. 0000000000000028 0000000000000008 WA 0 0 8
  70. [23] .data PROGBITS 00000000006008c8 000008c8
  71. 0000000000000010 0000000000000000 WA 0 0 8
  72. [24] .bss NOBITS 00000000006008d8 000008d8
  73. 0000000000000010 0000000000000000 WA 0 0 8
  74. [25] .comment PROGBITS 0000000000000000 000008d8
  75. 000000000000003e 0000000000000001 MS 0 0 1
  76. [26] .shstrtab STRTAB 0000000000000000 00000916
  77. 00000000000000e7 0000000000000000 0 0 1
  78. [27] .symtab SYMTAB 0000000000000000 00001140
  79. 0000000000000660 0000000000000018 28 47 8
  80. [28] .strtab STRTAB 0000000000000000 000017a0
  81. 000000000000025b 0000000000000000 0 0 1

由于输出过多,后面的结果并没有完全展示出来。ELF文件的主要内容就是由各个section及symbol表组成的。在上面的section列表中,大家最熟悉的应该是text段、data段和bss段。text段为代码段,用于保存可执行指令。data段为数据段,用于保存有非0初始值的全局变量和静态变量。bss段用于保存没有初始值或初值为0的全局变量和静态变量,当程序加载时,bss段中的变量会被初始化为0。这个段并不占用物理空间——因为完全没有必要,这些变量的值固定初始化为0,因此何必占用宝贵的物理空间?

其他段没有这三个段有名,下面来介绍一下其中一些比较常见的段:

·debug段:顾名思义,用于保存调试信息。

·dynamic段:用于保存动态链接信息。

·fini段:用于保存进程退出时的执行程序。当进程结束时,系统会自动执行这部分代码。

·init段:用于保存进程启动时的执行程序。当进程启动时,系统会自动执行这部分代码。

·rodata段:用于保存只读数据,如const修饰的全局变量、字符串常量。

·symtab段:用于保存符号表。

-------------------------------------程序是如何跑起来的--------------------

在Linux环境下,可以使用strace跟踪系统调用,此处以helloworld为例

  1. execve("./hello", ["./hello"], [/* 41 vars */]) = 0
  2. brk(0) = 0x151b000
  3. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f30733ef000
  4. access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
  5. open("/etc/ld.so.cache", O_RDONLY) = 3
  6. fstat(3, {st_mode=S_IFREG|0644, st_size=62458, ...}) = 0
  7. mmap(NULL, 62458, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f30733df000
  8. close(3)= 0
  9. open("/lib64/libc.so.6", O_RDONLY) = 3 //加载c语言库
  10. read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\356!\2478\0\0\0"..., 832) = 832
  11. fstat(3, {st_mode=S_IFREG|0755, st_size=1928936, ...}) = 0
  12. mmap(0x38a7200000, 3750184, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x38a7200000
  13. mprotect(0x38a738a000, 2097152, PROT_NONE) = 0
  14. mmap(0x38a758a000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x38a758a000
  15. mmap(0x38a7590000, 14632, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x38a7590000
  16. close(3) = 0
  17. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f30733de000
  18. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f30733dd000
  19. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f30733dc000
  20. arch_prctl(ARCH_SET_FS, 0x7f30733dd700) = 0
  21. mprotect(0x38a758a000, 16384, PROT_READ) = 0
  22. mprotect(0x38a701f000, 4096, PROT_READ) = 0
  23. munmap(0x7f30733df000, 62458) = 0
  24. fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
  25. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f30733ee000
  26. write(1, "hello world\n", 12hello world
  27. ) = 12
  28. exit_group(0) = ?
  29. +++ exited with 0 +++

下面就针对strace输出说明其含义。在Linux环境中,执行一个命令时,首先是由shell调用fork,然后在子进程中来真正执行这个命令(这一过程在strace输出中无法体现)。strace是hello_world开始执行后的输出。首先是调用execve来加载hello_world,然后ld会分别检查ld.so.nohwcap和ld.so.preload。其中,如果ld.so.nohwcap存在,则ld会加载其中未优化版本的库。如果ld.so.preload存在,则ld会加载其中的库——在一些项目中,我们需要拦截或替换系统调用或C库,此时就会利用这个机制,使用LD_PRELOAD来实现。之后利用mmap将ld.so.cache映射到内存中,ld.so.cache中保存了库的路径,这样就完成了所有的准备工作。接着ld加载c库——libc.so.6,利用mmap及mprotect设置程序的各个内存区域,到这里,程序运行的环境已经完成。后面的write会向文件描述符1(即标准输出)输出"Hello world!\n",返回值为13,它表示write成功的字符个数。最后调用exit_group退出程序,此时参数为0,表示程序退出的状态——此例中hello-world程序返回0。

--------------------------------------系统调用----------------------

系统调用是操作系统提供的服务,是应用程序与内核通信的接口,在早期Linux系统中,使用int 0x80陷入内核,相对于普通的函数调用来说,系统调用的性能消耗巨大。另外用户控件的程序默认是通过栈来传递参数,对于系统调用来说,内核态跟用户态使用的是不同的栈,因此,系统调用的参数只能通过寄存器的方式进行传递

------------------------------------C库函数---------------------------

Linux下,一般使用的C库是glibc,它封装了几乎所有的系统调用,下面以具体的系统调用open来看看glibc库是如何封装系统调用的。open在glibc中对应的实现函数是__open_nocancel

  1. int __open_nocancel(const char *file,int oflag,...)
  2. { int mode=0;
  3. if(oflagO_CREAT) {
  4. va_list arg;
  5. va_start(arg,oflag);
  6. mode=va_arg(arg,int);
  7. va_end(arg);
  8. }
  9. //系统调用编号
  10. return INLINE_SYSCALL(openat,4,AT_FDCWD,file,oflag,mode);
  11. }

其中INLINE_SYSCALL是我们关心的内容,这个宏完成了对真正系统调用的封装:INLINE_SYSCALL->INTERNAL_SYSCALL。实现INTERNAL_SYSCALL的一个实例为

  1. # define INTERNAL_SYSCALL(name, err, nr, args...) \
  2. ({ \
  3. register unsigned int resultvar; \  
  4. EXTRAVAR_##nr \
  5. asm volatile ( \  
  6. LOADARGS_##nr \
  7. "movl %1, %%eax\n\t" \
  8. "int $0x80\n\t" \  
  9. RESTOREARGS_##nr \
  10. : "=a" (resultvar) \
  11. : "i" (__NR_##name) ASMFMT_##nr(args) : "memory", "cc"); \
  12. (int) resultvar; })

「其中,关键的代码是用嵌入式汇编写的,在此只做简单说明。“move%1,%%eax”表示将第一个参数(即__NR_##name)赋给寄存器eax。__NR_##name为对应的系统调用号,对于本例中的open来说,其为__NR_openat。系统调用号在文件/usr/include/asm/unitstd_32(64).h中定义,「也就是说,在Linux平台下,系统调用的约定是使用寄存器eax来传递系统调用号的。至于参数的传递,在glibc中也有详细的说明,参见文件sysdeps/unix/sysv/linux/i386/sysdep.h。」

----------------------------------------可重入函数-------------------

「从字面上理解,可重入就是可重复进入。在编程领域,它不仅仅意味着可以重复进入,还要求在进入后能成功执行。这里的重复进入,是指当前进程已经处于该函数中,这时程序会允许当前进程的某个执行流程再次进入该函数,而不会引发问题。这里的执行流程不仅仅包括多线程,还包括信号处理、longjump等执行流程。所以,可重入函数一定是线程安全的,而线程安全函数则不一定是可重入函数。

从以上定义来看,很难说出哪些函数是可重入函数,但是可以很明显看出哪些函数是不可以重入的函数。当函数使用锁的时候,尤其是互斥锁的时候,该函数是不可重入的,否则会造成死锁。若函数使用了静态变量,并且其工作依赖于这个静态变量时,该函数也是不 可重入

来自为知笔记(Wiz)

时间: 2024-12-14 08:52:59

第0章Linux环境到内核基础知识的相关文章

unix环境高级编程基础知识之第一篇

陆陆续续看完了圣经第一章,熟悉了unix的整个编程流程,c语言的用处在这里得到伸张. 从unix的体系结构,原来操作系统包括内核及一些其他软件,我们常常误称为linux内核为操作系统,这俨然成为一种共识.基本熟悉了shell的介绍,主流的是bash(Bourne-again shell),unix的文件的基本操作,出错处理,用户ID,信号(感觉类似windows的消息),时间值,最后还有系统调用和库函数的区别. 自己把这篇的所有代码用vim的敲完了,主要前期是熟悉unix的基本命令编程,大致了解

TeraData环境搭建及基础知识

没办法,工作需要,得学习一下Teradata,现在就把平台搭建过程记录下来吧, 以便以后观看 一,平台搭建 1,首先安装VMware 2,下载Express VM版的Teradata,(Teradata Express 14.0 for VMware ) 地址:http://downloads.teradata.com/download/database/teradata-express/vmware 3,将文件解压,可以直接用VMware打开进入 二,配置 1,用户名密码都是root 2,配置

【RL-TCPnet网络教程】第38章 TFTP简单文件传输基础知识

第38章      TFTP简单文件传输基础知识 本章节为大家讲解TFTP(Trivial File Transfer Protocol,简单文件传输协议)的基础知识,方便后面章节的实战操作. (本章的知识点主要整理自网络) 38.1  初学者重要提示 38.2  TFTP基础知识参考资料 38.3  TFTP基础知识点 38.4  总结 38.1  初学者重要提示 TFTP简单文件传输协议在实际项目中有比较重要的实用价值,需要初学者对TFTP的基础知识也有个认识. 38.2  TFTP基础知识

SQL Server之 (一) 数据库简介 SQL Server环境配置 数据库基础知识

   前言 这个是我工作两年多后,再次从最基础的SQL入门开始,认真的学一遍SQL Server,捡漏和巩固都有;因为自己刚开始学的时候,总是心烦气躁,最近换工作,发现1到2年经验,问到基础性的东西还是很多,这个时候需要的是扎实的基础功夫,所以一系列打击+反省后,自己节假日在家从最基础重新认识一下SQL Server,继续沉淀一下.哪里有不对或需深入探讨,请直接留言或者小窗我;欢迎~ (一) 数据库简介   SQL Server环境配置   数据库基础知识 1.什么是数据库,数据库有哪些特点,为

【RL-TCPnet网络教程】第35章 FTP文件传输协议基础知识

第35章      FTP文件传输协议基础知识 本章节为大家讲解FTP(File Transfer Protocol,文件传输协议)的基础知识,方便后面章节的实战操作. (本章的知识点主要整理自网络) 35.1  初学者重要提示 35.2  FTP基础知识参考资料 35.3  FTP基础知识点 35.4  总结 35.1  初学者重要提示 FTP文件传输协议在实际项目中有比较重要的实用价值,需要初学者对FTP的基础知识也有个认识. 35.2  FTP基础知识参考资料 大家可以从以下地址获得FTP

Linux运维是什么?linux运维的基础知识

如果您对运维行业了解一些,应该会知道,现在的运维早已不是早年的"睡机房",往办公室打眼一看,分不清是运维攻城狮还是开发程序猿,但是,运维这行也是春天到了,今天Linux,明天云计算的,各种新鲜概念层出不穷,那么,Linux运维是什么?云计算运维又是什么? 现在我们谈运维,经常谈的就是海量这个词,当一个企业拥有几百台服务器的时候,可能更关注的是如何满足应用/业务需求,更多时候不必过多的关注架构.容量.扩展性这些,运维部门有时甚至沦为打杂部门.但是当一个企业拥有几万甚至几十万台的服务器这个

unix环境高级编程基础知识之第四章

1.从当前用户转到root用户:直接输入su命令,然后输入root密码,如果之前没有设置root命令密码会登陆不成功,这里需要命令sudo passwd命令设置密码,然后按照上面输入就成:从root命令转普通用户:使用exit命令就可以.使用logout会有问题bash: logout: not login shell: use `exit',意思是你当前不是login shell,要用exit命令退出.当使用root下使用命令登陆login用户的时候,可以使用logout命令退出,已验证. 2

第四章 Linux环境

程序参数 int main(int argc , char *argv[]) argc是程序参数的个数,argv是代表参数的字符串数组. 以下对参数检查: //args.c#include<stdio.h> #include<stdlib.h> int main(int argc,char *argv[]){ int arg; for(arg=0;arg<argc;arg++){ if(argv[arg][0] == '-'){ printf("option:%s\

Bugfree3.0.4 Linux环境安装指南

一. 安装apache服务器 1. 检查apache服务器是否安装 service httpd status 2. 如提示未被识别的服务,则表明组件未安装,需手动安装 yum install httpd 3. 安装完成后启动进程 service httpd start 4. 验证apache服务器是否正常运行 在保证httpd进程正常运行的前提下,在httpd的根目录/var/www/html下,新建一个静态网页,如index.html,然后在浏览器上输入如192.168.128.144/ind