在学习UNIX操作系统的过程中,对重点知识进行整理,对存在的疑问查找资料后解决并记录,便于日后的定期回顾复习。
从源代码到可执行程序的构建过程?
预编译/编译预处理(gcc -E hello.c -o hello.i)(头文件扩展、宏扩展)
编译(gcc -S hello.i -o hello.s)(将高级语言翻译成汇编语言,得到汇编文件)
汇编(gcc -c hello.s -o hello.o)(将汇编语言翻译成机器指令,得到目标模块)
链接(gcc hello.o -o hello)(将目标模块和标准库链接,得到可执行程序)
Unix是什么?
Unix是1970年出现的操作系统。
Linux是基于Unix的系统,
Unix有三大流派:System V(例如AIX用于银行等)、Berkley(例如OpenBSD,Mac OS X)、Hybrid(例如:Minix、Linux(GPL,免费开源))
Linux有很多发行版本(例如:Ubutu\Fedora\Debian\RedHat)。
Unix和Linux区别?
Linux是基于UNIX的操作系统。
Linux来源,可免费试用,应用广泛,Unix不开源,需要授权,注意用于服务器,工作站等。
为什么讲解Unix操作系统?
如何下载服务器页面,播放视频,这就不是靠语言能解决,而是依赖操作系统了。
操作系统的内容涉及:进程、线程、信号、网络。
gcc编译器的特点是什么?
支持多种硬件架构、多种操作系统、多种编程语言。
gcc编译器的作用是什么?
将源代码编译成可执行文件。
Gcc命令后可以带一些选项,用来显示警告或警告变为错误等。
头文件中可以包含什么?
头文件卫士、变量声明、函数声明、宏定义、自定义数据类型(结构体等)、数据类型别名(typedef)、包含其他头文件。
头文件卫士的作用是什么?
防止宏和数据类型反复定义。
头文件的存放在什么位置?
系统目录(使用<>)、当前目录(使用双引号)、附加目录(使用命令指定)。
附加目录如何指定?
使用命令gcc -I路径
预处理指令是什么?有哪些?
预处理指令是告诉编译器在编译之前预先处理的一些指令,有宏定义,文件包括,条件编译。预处理指令一般以 # 号开头,能够出如今文件的不论什么地方,作用于整个文件。
预处理指令中包含:头文件包含(#include)、宏定义(#define)、条件编译(#if等)、其他指令(#pragma、#error、#warning、#line)。
#pragma预处理指令的作用是什么?
1)#pragma once:一次性编译(目的是防止头文件报包含多次,作用类似#ifnef,但有细微区别)
区别是:
#pragma是编译器提供的:同一文件不会被编译多次,这里同一文件是指物理上的文件。部分编译器会不支持。
#ifndef,#define是C/C++语言中的宏定义,依赖于宏名字不能冲突,也能保证内容完全相同的两个文件不会被不小心同时包含。所有支持C++的编译器都有效。
如果是跨平台的,最好是使用#ifndef这种。
2)#pragma GCC dependency <被依赖文件>:指定文件依赖(表示当前文件依赖于指定的文件,如果当前文件的最后一次,修改的时间早于依赖的文件,则产生警告信息)
3)#pragma GCC posion goto:它的作用就是不能使用goto
4)#pragma pack(1/2/4/8....) :字节对齐,例如结构体声明的时候
环境变量是什么意思?
Unix是一个多用户的操作系统,每个用户登录系统之后,都会有一个专用的运行环境。通常每个用户默认的环境都是相同的。这个默认的环境实际上就是一组环境变量的定义,用户可以对自己的运行环境进行定制,其方法就是修改相应的系统环境变量。
添加环境变量:
$ export PATHNAME=value
查看环境变量:
$ env | grep PATHNAME
剩余疑问:
什么时候需要配置环境变量?
什么是静态库?
库中有多个文件,实际项目中每个模块都有对应的代码文件。
1)静态库是将多个目标文件(.o文件)打包成一个文件。
2)静态库的特点是什么?
在编译阶段进行打包,是将代码都复制过来,因此占用空间大,但是执行效率高;
3)静态库的形式是:libXXX.a
4)构建静态库:
先使用gcc -c AAA.c生成AAA.o文件,其他文件同理
使用命令ar -r libXXX.a AAA.o BBB.o CCC.o。例如:生成libhello.a的文件
5)链接静态库:
使用gcc -c main.c生成main.o文件。
链接命令:gcc main.o -lhello -o main
什么是动态库?
1)也叫共享库。不复制函数,复制的是函数地址。
2)动态库的形式是:libXXX.so
3)构建动态库:
命令:
编译:gcc -c -fpic calc.c
gcc -c -fpic show.c
链接:gcc -shared calc.o show.o -o libmath.so
4)链接静态库:
命令:gcc main.o -lmath -L. -o main
注意要配置环境变量:
$ export LIBRARY_PATH=.
$ export LD_LIBRARY_PATH=.
静态库和动态库的区别?
静态库是将代码拷贝一份再链接,生成文件相对大;动态库是编译时直接链接函数地址,耗费时间相对长。相比较而言,动态库优势明显。因此,gcc编译器,缺省使用动态库版本。
什么是动态库的动态加载?
在代码运行的时候,找到动态库,以一种更加动态的方式加载动态库。
需要先有动态库的文件(libXXX.so)。
1)使用函数dlopen加载动态库,把动态库从磁盘读取到内存中。该函数的返回值是动态库句柄。含义是:操作系统中保存有键值对形式的映射表,拿着句柄给系统,系统去找到内存中的动态库,句柄的优点是防止人为修改。
2)通过dlsym函数将函数名传递进去,返回void*类型的指定函数的地址,将void*转换为实际函数的类型,例如:int*或者double*等。
3)手动调用dlclose卸载动态库,传递参数是加载时的句柄。因为动态库是共享的,其他地方也会调用,所以系统采用一种引用计数的方式来判断释放时间。
4)使用函数dlerror可以获取错误信息。
有哪些辅助工具?
1)查看符号表:命令:nm 目标模块/可执行程序/静态库/动态库:查看其中的符号(函数和全局变量)
其他辅助:反汇编;消除冗余信息;查看动态库依赖。
什么是错误处理?
当代码出现错误时,进行友好提示,避免系统出现不加任何提示的崩溃现象。
如何进行错误处理?
1)先判断函数是否出错。
借助man命令,可以看到函数的错误返回值等信息。例如man malloc。
2)使用标准库预定义的全局变量:errno错误号
所在头文件是#include <errno.h>
3)将错误号转换为字符串。
需要包含一个头文件string.h
函数原型是:char* strerror(int errnum)
注意:当一处发生错误时,会更新errno,没错误则不更新,其保留的信息还是上一次的错误,故需要当函数返回错误状态时,再根据错误号判断详情。
环境变量表是什么?
在每一个进程中都有一张表,这个表里存储这和进程相关的审计信息,也称之为进程表项。其本质是一个以NULL指针结尾的字符指针数组,每个字符指针类型的数组元素都指向一个以空字符(‘\0’)结尾的字符串。
1)有一个全局变量environ,它是环境变量的首地址,拿着它依次(循环)往下可以找到整张表的所有元素。
2)main函数的第三个参数:char* envp[],其保存的也是环境变量表。
3)可以获取已有环境变量的值,使用函数:qetenv
4)也可以修改或新增环境变量,使用函数:putenv
5)删除环境变量,使用函数:unsetenv
6)清空环境变量表,使用函数:clearenv
操作系统层面的内存管理?
不论是用户层、系统层还是内核层,都需要和内存打交道。
用户层包含C、C++、STL这些,例如C中使用malloc分配内存。
系统层包含POSIX、Linux。
内核层包含系统内核、硬件驱动、硬件。
无论哪一层都用其独特的方式和内存建立联系。
什么是进程?
程序是一个文件的概念,程序要想执行起来,就得读到内存中,形成进程。
进程映像是什么?
进程在内存中布局的格式,就叫进程映像。
从高地址到低地址依次从上往下:
栈区(可写:非静态局部变量)【注意栈区的空间】
堆区(可写:动态内存分配)
BSS区(可写:不带常属性且未初始化的全局变量和静态局部变量)
数据区(可写:不带常属性且被初始化的全局变量和静态局部变量)
代码区(只读:可执行指令、字面值常量、带有常属性且被初始化的全局变量和静态局部变量)
Unix C的库:<unistd.h> :IEEE(国际电气工程师协会)制定的POSIX标准,规定命令一致
什么是ELF格式?
ELF是指可执行可链接文件格式,它包括目标模块(.o)、静态库(.a)、动态库(.so)、可执行程序这些Linux系统的二进制模块。
进程映像就是按照ELF格式,对每个区进行读取。
如何从系统层面理解编译链接和加载?
至此:
我们在站在系统层面再理解一下,所谓编译和链接就是用高级语言编写的文本格式的源代码文件翻译成二进制的机器指令,最终形成ELF格式的可执行文件的过程。
所谓可执行程序的加载就是将保存在磁盘上的ELF格式的可执行文件读入内存形成进程映像的过程。
何为虚拟内存?
先来看下网上的解释:虚拟内存是计算机系统内存管理的一种技术。
它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储上上,在需要时进行数据交换。大多数操作系统都使用了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。