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等执行流程。所以,可重入函数一定是线程安全的,而线程安全函数则不一定是可重入函数。
从以上定义来看,很难说出哪些函数是可重入函数,但是可以很明显看出哪些函数是不可以重入的函数。当函数使用锁的时候,尤其是互斥锁的时候,该函数是不可重入的,否则会造成死锁。若函数使用了静态变量,并且其工作依赖于这个静态变量时,该函数也是不 可重入





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

  1. 【菜鸟学习Linux】-第三章- Linux环境搭建-使用VMware9安装Ubuntu 12.04系统

    上一步,我们安装了VMware9虚拟机,现在我们就是用它来安装Ubuntu12.04系统,至于Ubuntu是什么,我就不废话了,大家google一下,比我讲的清楚,好了,开始干活! Ubuntu官网下 ...

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

    第38章      TFTP简单文件传输基础知识 本章节为大家讲解TFTP(Trivial File Transfer Protocol,简单文件传输协议)的基础知识,方便后面章节的实战操作. (本章 ...

  3. 【RL-TCPnet网络教程】第16章 UDP用户数据报协议基础知识

    第16章      UDP用户数据报协议基础知识 本章节为大家讲解UDP(User Datagram Protocol,用户数据报协议),需要大家对UDP有个基础的认识,方便后面章节UDP实战操作. ...

  4. 【STM32H7教程】第47章 STM32H7的FMC总线基础知识和HAL库API

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第47章       STM32H7的FMC总线基础知识和HA ...

  5. 【RL-TCPnet网络教程】第41章 HTTP超文本传输协议基础知识

    第41章      HTTP超文本传输协议基础知识 本章节为大家讲解HTTP(HyperText Transfer Protocol,超文本传输协议),从本章节开始,正式进入嵌入式Web的设计和学习. ...

  6. 【STM32H7教程】第32章 STM32H7的TIM定时器基础知识和HAL库API

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第32章       STM32H7的TIM定时器基础知识和H ...

  7. 【STM32H7教程】第29章 STM32H7的USART串口基础知识和HAL库API

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第29章       STM32H7的USART串口基础知识和 ...

  8. Windows内核基础知识-1-段寄存器

    Windows内核基础知识-1-段寄存器 学过汇编的应该都知道段寄存器,在Windows里段寄存器有很多,之前可能只接触了ds数据段,cs 代码段这种,今天这个博客就介绍Windows一些比较常用的段 ...

  9. Windows内核基础知识-2-段描述符

    Windows内核基础知识-2-段描述符 比如: ES 002B 0(FFFFFFFF) 意思就是es段寄存器,段选择子/段选择符 为002B, 起始地址base为0, 限制范围Limit地址最大能寻 ...

随机推荐

  1. iOS 打印系统字体

    NSArray * array = [UIFont familyNames]; for( NSString *familyName in array ){ printf( "Family: ...

  2. iOS 绘制1像素的线

    一.Point Vs Pixel iOS中当我们使用Quartz,UIKit,CoreAnimation等框架时,所有的坐标系统采用Point来衡量.系统在实际渲染到设置时会帮助我们处理Point到P ...

  3. NOIP模拟赛 无线通讯网

    [题目描述] 国防部计划用无线网络连接若干个边防哨所.2种不同的通讯技术用来搭建无线网络:每个边防哨所都要配备无线电收发器:有一些哨所还可以增配卫星电话. 任意两个配备了一条卫星电话线路的哨所(两边都 ...

  4. 【NOIP2017提高A组冲刺11.8】好文章

    #include<algorithm> #include<iostream> #include<cstring> #include<cstdio> us ...

  5. Pandas中数据的处理

    有两种丢失数据 ——None ——np.nan(NaN) None是python自带的,其类型为python object.因此,None不能参与到任何计算中 Object类型的运算比int类型的运算 ...

  6. 将远程分支拷贝到本地,并更新代码push到原分支

    第一步:git clone +主分支 第二步:git fetch origin 分支名 第三步:git checkout -b 分支名 origin/分支名 第四步:git pull origin 分 ...

  7. HTTP-常用配置

    前言 这篇主要介绍HTTP服务程序环境 可能有一些介绍不到,博主能力有限,欢迎大神来纠正改进 HTTP协议从http/0.9到如今的http/2.0中间发生了很大的改变,现在主流的事http/1.1 ...

  8. Hive中集合类型

    Hive中集合类型 创建表,集合是以 - 分割的 数据文件 加载数据 查询数据 查询数组中第一个字段 再建一个表,使用map 查看数据文件 加载数据 查询数据 查询键值 创建表,struct类型 查看 ...

  9. oracle 控制文件的重建

    目录 oracle 控制文件的重建 NORESETLOGS RESETLOGS oracle 控制文件的重建 不到最后时刻,如三个控制文件都已损坏,又没有控制文件的备份.还是不要重建控制文件,处理不好 ...

  10. 根据已经commit的数据,进行leader和peon之间的同步

    Leader Election基本设计 按照rank表示优先级解决冲突问题,为每个monitor预先分配了一个rank 只会接受优先级(rank)比自己高.epoch比上次已接受的epoch大的选举请 ...