Linux C与C++一线开发实践之一 Linux概述与Linux C++开发

Posted 夜色魅影

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux C与C++一线开发实践之一 Linux概述与Linux C++开发相关的知识,希望对你有一定的参考价值。

Linux系统启动的基本过程

对于一台Linux系统来说,用户按下开机按钮后,一共要经历如下几个过程。

上电 Bios自检 系统引导 启动内核

下面我们来详细分析这几个过程:

  1. 按下电源
    上电后,CPU的RESET引脚会由特殊的硬件电路产生一个逻辑值,这就是常说的CPU复位,此时CPU被唤醒了,CPU将在0xfffffff0处执行一条长跳转指令,直接跳转到固化的ROM中的启动代码处(这里代码软件就是BIOS),并开始执行BIOS程序。

  2. BIOS自检
    早期BIOS芯片确实只是“只读内存”(Read-Only Memory, ROM),里面的内容是用一种烧录器写入的,一旦写入就不能更改,除非更换芯片。现代的主板都是用一种叫Flash EPROM的芯片来存储系统BIOS,里面的内容可使用主板厂商提供的擦写程序擦除后重新写入,这样就给用户升级BIOS提供了极大的方便。
    2.1硬件自检
    BIOS程序的主要作用就是硬件自检,主要负责检测系统外围关键设备(如CPU、内存、显卡、I/O、键盘鼠标等)是否正常。比如,我们常见的内存松动的情况,BIOS自检阶段就会报错。自检中报错将按两种情况处理:一,对于严重故障,直接停机,此时由于各种初始化操作还没完成,因此不能给出任何提示。二,对于非严重故障则给出提示或声音报警,等待用户处理。如果没有问题,屏幕上就会打印显示出CPU、内存、硬盘等信息。
    2.2查找引导设备
    硬件自检完成后,然后按照一定的顺序启动这些硬件设备(常设置的启动顺序就是指这个),并找到下一阶段的启动程序(系统引导)将控制权交给它。

  3. 系统引导(查找系统位置)
    BIOS找到引导程序把控制权交给它并把它载入内存后就退出了。在接着讲系统引导之前我们先来了解下这个引导程序到底在哪里加载的呢?
    我们存储设备前512个字节被定义为“主引导记录”。可以根据这段记录确定我们要引导的操作系统位于硬盘的那个位置,然后启动它。

主引导分区示意图:

1~446字节 机器码447~510字节 分区表511、512字节 记录标志

其中分区表的作用是将硬盘分成若干个区。它的长度只有64个字节,里面又分成4项,每项16个字节。所以,一个硬盘最多只能分4个一级分区,又叫“主分区”。每个主分区的16字节由下面6部分组成。

1字节 是否为激活分区2~4字节 第一个扇区物理位置5字节 主分区类型6~8字节 最后一个扇区物理位置9~12字节 第一个扇区逻辑地址13~16字节 扇区总数

最后4个字节决定了这个主分区的长度。最多不超过2的32次方,如果每个扇区为512字节,就意味着单个分区最大不超过2TB。再考虑到扇区的逻辑地址也是32位,所以单个硬盘可利用的空间最大也不超过2TB。如果想使用更大的硬盘就只有两个办法:一是提高每个扇区的字节数,二是增加扇区总数。
随着硬盘越来越大,4个主分区已经不够了,然后引进了扩展分区的概念。规定4个主分区中有且只有一个可以定义为扩展分区,这个扩展分区可以包含一个或多个逻辑分区。

最后说说主引导分区中最后两个字节,如果最后两个字节是0x55和0xAA,就表明可以启动,操作系统在这个硬盘上。至此系统引导结束,控制权交给操作系统。

  1. 启动内核
    控制权交给操作系统后,操作系统的内核首先被载入内存。先载入/boot目录下面的内核文件,内核加载成功后,第一个运行的程序是/sbin/init。它根据配置文件(/etc/initab)产生init进程。这是Linux启动的第一个进程,PID=1。然后init进程加载系统的各个模块,比如窗口程序和网络程序,直至执行/bin/login程序,跳出登入界面,等待用户输入用户名和密码。至此,全部启动过程完成。

环境变量

环境变量通常是指在操作系统中用来指定操作系统运行环境的一些变量,比如PATH变量包含一系列由冒号分隔开的目录,系统就是从这些目录里寻找可执行文件的。
环境变量分为:系统环境变量和用户环境变量。
(1)系统环境变量存放在/etc/profile文件中。该文件作用于登入到系统的每一个用户。修改该文件中的环境变量将作用于系统的每个用户。
(2)用户环境变量存放在各个用户的~/.bash_profile文件中,~表示每个用户的宿主目录(home目录)。.bash_profile是一个隐藏文件,我们在进入~后,通过ls -a可以看到该文件。修改该文件中的变量将仅影响该文件对应的用户。
可以用env命令来显示当前用户的环境变量。

Linux下gcc/g++编译器使用

编译过程分析

gcc是可以在多种硬件平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比,平均效率要高20%~30%。很多人都知道它可以编译C语言源程序,其实还可以用来编译C++语言源程序。虽然另一个C++编译工具g++更专业些,但作为一个C++开发者,学会gcc工具的使用也是必备技能。
首先我们来分析下一个C语言源程序到可执行程序的过程。如下:

汇编--二进制 预处理 编译 链接 可执行程序

我们还是以Hello world示例代码来分析这几个过程。

//文件名:test.c
#include <stdio.h>

int main(int argc, char *argv[])

        char sz[] = "Hello world!\\n";
        printf("%s", sz);
        fflush(stdout);
        return 0;

  1. 预处理
    预处理就是对源程序中的伪指令(以#开头的指令)和特殊符号进行处理的过程。伪指令包括宏定义指令、条件编译指令和头文件包含指令。gcc对C源文件进行预处理后会输出.i文件。
    预处理过程主要是处理源代码中以#开始的预编译指令,处理规则如下:
    (1)将所有的#define宏定义替换
    (2)处理所有条件编译指令,如#if、#ifdef等。
    (3)将#include文件插入,该过程是递归进行的,被包含的文件可能还包含其他文件。
    (4)删除所有的注释
    (5)添加行号和文件标识,以便于编译时编译器产生调试用的行号信息及编译时产生编译错误或警告时能够显示行号信息。
    (6)保留所有的#pragma编译器指令,因为编译器需要使用它们。

下面是我们通过gcc编译的一个预处理示例:

gcc -E test.c -o test.i   //-E告诉gcc只进行预处理  -o是输出结果文件

通过cat -n test.i查看生成的test.i预处理文件最后部分截图:

  1. 编译阶段
    编译阶段就是把预处理文件进行一系列的词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。默认情况下,这个文件不会生成,而是直接把它接着处理生成二进制文件。
    如果想看这个中间汇编代码文件可以用-S选项指定生成,汇编代码文件后缀是.s文件。
gcc -S test.i -o test.s  //把预处理文件生成汇编文件

同样用cat命令查看,部分截图如下:

接着来看看汇编代码怎么到二进制文件的。
每一个汇编语句几乎都对应一条机器指令。所以这个过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。
gcc生成的二进制代码文件后缀名为.o的文件。我们把上面的test.s文件编译为test.o文件,通过指令-c生成:

gcc -c test.s -o test.o  //汇编代码到二进制代码(windows上对应的是obj文件)

二进制文件(不是文本文件) ,不能用cat查看,可以通过命令hexdump查看。

  1. 链接阶段
    链接主要是为了解决多个文件之间相互引用的问题。上面编译阶段只对单个文件进行处理,如果这个文件里面需要引用到其他文件中的符号(全局变量或者函数库中的某个函数),那么这时在这个文件中该符号的地址是没法确定的,只能等链接器把所有的目标文件连接到一起后才能确定最终的地址,最终生成可执行的文件。
    在链接阶段,所有的目标文件被安排在可执行程序中的恰当位置。要注意一点是,在Linux中,可执行文件没有统一的后缀,系统是根据文件属性来区分是否是可执行文件的。
    下面我们对test.o进行链接:
//链接生成可执行文件test
gcc test.o -o test  

//如果有多个目标文件,可以写一起中间加空格隔开
gcc test1.o test2.o test3.o -o test


至此我们对test.c源程序到生成可执行程序整个过程就分析完了。当然实际开发中我们不需要这么麻烦,只需要gcc test.c -o test就可以了,它并不会生成那些中间文件。

gcc常用命令参数

gcc选项有上百个,当然很多都用不到,我们只需熟悉一些常见的即可。还有注意gcc选项区分大小写,比如-o与-O,前者是生成一个结果文件,后者表示对生成的可执行文件进行一级优化。

  1. 没有任何选项
    gcc test.c,它会默认生成可执行文件a.out。

  2. 选项-x
    告诉gcc编译的源文件是什么语言,而不用根据其后缀去判断。这主要用于我们不想暴露源文件类型的场景,如我们把c源程序命名为test.pig,我们可以这样编译:
    gcc -x c test.pig

  3. 选项-o
    用于指定要生成的结果文件,结果文件可以是预处理文件、汇编文件、目标文件、或者最终的可执行文件。

  4. 选项-c
    对源文件进行编译,但不进行链接。此时,将生成目标文件,如果没有指定输出文件名,就会默认生成同名的.o文件。
    比如:gcc -c test.cpp
    生成:test.o

  5. 选项-I
    指定头文件所在路径,就是include头文件的查找路径。说到这里,顺便说下include <test.h>与include "test.h"的区别,就是查找头文件路径有点区别。
    <>尖括号形式,首先去-I指定的路径找,然后到标准默认路径/usr/local/include下找,最后到/usr/include找,都找不多则报错。
    “”双引号形式,只是首先它会在当前工作目录找(源程序所在目录),然后后面和<>形式一样了。

  6. 选项-include
    在命令中也可以包含头文件。这样我们可以在源码中隐藏头文件的include。格式如下:
    gcc [srcfile] -include [headfile]

  7. 选项-Wall
    显示所有的警告信息。
    例: gcc test.cpp -Wall -o test

  8. 选项-g
    产生供gdb调试用的可执行文件。因此,加了这个选项后,产生的可执行文件尺寸要大些。
    例:gcc test.cpp -g -o test

  9. 选项-pg
    产生供gprof剖析用的可执行文件。gprof是Linux下对C++程序进行性能分析的工具。

  10. 选项-l
    用来链接共享库。例如编译一个使用了C++标准库内容的程序,则需要链接C++标准库,如:
    gcc test.cpp -lstdc++ -o test
    其中,stdc++是C++标准库名字,它和l之间没有空格。

最后g++的使用其实和gcc差不多,对于c++程序g++使用更简单。像上面提到的gcc编译c++可能要-l来链接c++库,而g++就不需要,它会自动链接。

Linux下 gdb调试

首先被gdb调试的可执行程序要是加了-g选项的调试版本才能调试。下面我们我列出几个常用的调试命令:

启动:gdb-->file xx或者gdb xx
退出:quit
list或l:显示代码(可以跟两个参数,指定显示代码范围)
break n:下断点
delete [n]:删除断点
info break:显示断点清单
info local:显示当前函数中的局部变量信息
run:运行启动的程序
c:继续运行
next:下一步不进入函数内部
step:下一步进入函数内部
backtrace:显示堆栈信息
print:打印变量的值

更详细的可以通过help帮助命令查看。

以上是关于Linux C与C++一线开发实践之一 Linux概述与Linux C++开发的主要内容,如果未能解决你的问题,请参考以下文章

Linux C与C++一线开发实践之四 Linux进程间的通信

Linux C与C++一线开发实践之二 Linux文件系统

Linux C与C++一线开发实践之三 Linux多进程

Linux C与C++一线开发实践之三 Linux多进程

Linux C与C++一线开发实践之六 多线程高级编程

Linux C与C++一线开发实践之六 多线程高级编程