Unix系统
Linux
类Unix系统,免费开源,隶属于GNU工程,GNU = GNU Not Unix
Linux系统版本
- 早期版本:0.01,0.02,…,1.00
- 旧计划:1.0.1,…,2.6.0 (A.B.C)
- A - 主版本号,内核大幅更新
- B - 次版本号,内核重大修改,奇数测试版,偶数稳定版
- C - 补丁序号,内核轻微修改
- 新计划:A.B.C-D.E
- D - 构建次数,反映极微小的更新
- E - 描述信息
查看Linux系统版本
# cat /proc/versionLinux version 5.8.0-36-generic (buildd@lgw01-amd64-027) (gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #40~20.04.1-Ubuntu SMP Wed Jan 6 10:15:55 UTC 2021
Linux系统的特点
- 遵循GNU/GPL许可
- 开放性
- 多用户
- 设备无关性
- 丰富的网络功能
- 可靠的系统安全
- 良好的可移植性
Linux发现版本
- Ubuntu - 大众化,简单易用
- Linux Mint - 新潮前位
- Fedora - Red Hat的桌面版本
- openSUSE - 华丽
- Debian - 自由开放
- Slackware - 朴素简洁,简陋
- Red Hat - 经典,稳定,企业应用,支持全面
GNU编译器(gcc)
- 支持多种硬件架构:x86-64、Alpha、ARM、PowerPC、SPARC、VA、…
- 支持多种操作系统:Unix、Linux、DSB、Android、Mac OS、iOS、Windows
- 支持多种编程语言:C、C++、Objective-C、Java、Fortran、Pascal、Ada、…
gcc版本
# gcc -v
编译
源代码(.c)-预编译->头文件和宏扩展-编译->汇编码(.s)-汇编->目标码(.o)-链接->可执行代码(a.out)
// hello.c 文件编译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 // 获得可执行代码
文件名
.h:C语言源代码头文件.c:C语言源代码文件.s:汇编语言文件.o:目标文件.a:静态库文件.so:共享(动态)库文件.out:可执行文件
编译选项
gcc [选项] [参数] 文件1 文件2 ...
-o:指定输出文件-E:预编译,缺省输出到屏幕,用-o指定输出文件-S:编译,将高级语言文件编译成汇编语言文件-c: 汇编,将汇编语言文件汇编成机器语言文件-Wall:产生全部警告-Werror:将警告作为错误处理-x:指定源代码的语言- 默认:.c C语言,.cpp C++语言,.for Fortran语言,.java Java语言
- -lstd 指定库
-O:指定优化等级,-O0不优化,-O1缺省优化(速度大小折中),-O2强调速度,-O3强调大小-I:指定头文件搜索路径-D:宏定义
头文件
头文件包含内容
- 头文件卫士:https://www.yuque.com/jin1024/wg7xed/bnx3ba#t
- 包含其它头文件
- 宏定义:https://www.yuque.com/jin1024/wg7xed/bnx3ba#obi5b
- 自定义类型:struct、enum、union
- 类型别名:typedef
- 外部变量声明:extern
- 函数声明
头文件查找路径
gcc -I 头文件附加搜索路径增加头文件搜索路径#include <头文件>:先找-I指定目录 -> 再找系统目录#include "头文件":先找-I指定目录 -> 再找当期目录 -> 最后找系统目录
头文件系统目录:
/usr/include:标准C库/usr/local/include:第三方库/usr/lib/gcc/.../../include:编译器库
预处理指令
https://www.yuque.com/jin1024/wg7xed/bnx3ba#5Ewj7
#include- 将指定的文件内容插至此指令处#define- 定义宏#undef- 删除宏#if- 如果#ifdef- 如果宏已定义#ifndef- 如果宏未定义#else- 否则,与#if/#ifdef/#ifndef配合使用#elif- 否则如果,与#if/#ifdef/#ifndef配合使用#endif- 结束判定,与#if/#ifdef/#ifndef配合使用#error- 产生错误,结束预处理#warning- 产生警告,继续预处理#line- 指定行号#pragma- 设定编译器的状态或者指示编译器的操作 ```c 格式:pragma GCC dependency 被依赖文件
例子: main.c …
pragma GCC dependency “dep.c”
…
如果 dep.c 文件更新实现 晚于 main.c 则gcc编译会出现warning警告
格式:
pragma GCC poison 语法禁忌
例子: …
pragma GCC poison goto //静止使用goto
…
不能使用goto语言,否则gcc编译报错
格式:
pragma pack(按几字节对齐:1/2/4/8)
pragma pack() - 按缺省字节数对齐
例如:
pragma pack(1)
struct A { double d; // 8字节 int i; // 4字节 char c; // 1字节, 未设置pack则为4字节 short h; // 2字节, 未设置pack则为4字节 }
pragma pack() // 回复默认
<a name="m089Z"></a>## 预定义宏`__BASE_FILE__` : 正在被处理的源文件名<br />`__FILE__` : 所在文件名<br />`__LINE__` : 所在行的行号<br />`__FUNCTION__` : 所在函数的函数名<br />`__func__` : 同 `__FUNCTION__` <br />`__DATE__` : 处理日期<br />`__TIME__` : 处理时间<br />`__INCLUDE_LEVEL__` : 包含层数,从0开始<a name="UOi3e"></a>## 环境变量命令行中查看环境变量: `env`- `C_INCLUDE_PATH` : C语言头文件的附加搜索路径,相当于-I选项。- `CPATH` : 同 `C_INCLUDE_PATH`- `CPLUS_INCLUDE_PATH` : C++语言头文件的附加搜索路径,相当于-I选项。- `LIBRARY_PATH` : 链接库路径- `LD_LIBRARY_PATH` : 加载库路径#include "/.../.../xxx.h" - 移植性差<br />#include "xxx.h"<br />gcc -I/.../... ... - 推荐<br />C_INCLUDE_PATH/CPATH=/.../...:/... - 易冲突<a name="cmPmt"></a># 库- **单一模型**:将程序中所有功能全部实现于一个单一的源文件内部。编译时间长,不易于维护和升级,不易于协作开发。- **分离模型**:将程序中的不同功能模块划分到不同的源文件中。缩短编译时间,易于维护和升级,易于协作开发。<a name="87BWA"></a>## 静态库- 静态库本质就是将多个目标文件打包成一个文件- 链接静态库就是将库中被调用的代码复制到调用模块中- 缺点:程序通常会占用较大空间,库中代码一旦修改,所有使用该库的程序必须重新链接- 优点:程序运行无需依赖库,其执行效率高。- 文件形式:libxxx.a- 构建静态库:.c -> .o -> .a- `ar -r libxxx.a x.o y.o z.o`- 使用静态库- `gcc -l库名 -L库路径`- 库名:如果库文件为 `libxxx.a` ,则库名为 `xxx`<a name="td3FP"></a>## 动态(共享)库- 动态库和静态库最大的不同是链接动态库并不需要将库中被调用的代码复制到调用模块中,相反嵌入到模块中的仅仅是被调用代码在动态库中的相对地址。- 如果动态库中的代码同时为多个进程使用,动态库的实例在整个内存空间中仅需一份,因此动态库也叫共享库或共享对象(Shared Object, so)- 优点:使用动态库的模块所占空间较小,即使修改了库中的代码,只要**接口保存不变**,**无需从新链接。**- 缺点:使用动态库的代码在运行时在运行时需要依赖库,执行效率略低- 构建动态库:- gcc -c -fpic xxx.c- `-fpic` 生成位置无关码,库内部的函数调用也用相对地址表示- gcc -shared -o libxxx.so x.o y.o z.o- 使用库和静态库相同,运行时所调用的动态库必须位于 `LD_LIBRARY_PATH` 环境变量所表示的路径中- gcc 默认使用动态库,强制使用静态库 `-static`<a name="sjcLo"></a>## 动态加载动态库```c// 需包含该头文件#include <dlfcn.h>// GCC编译时 加 -ldl 参数//jgh 获取动态库句柄// filename: 动态库路径,若只给文件名,则根据LD_LIBRARY_PATH环境变量搜索动态库// flag: 加载方式,可去以下值// RTLD_LAZY: 延迟加载(懒加载),使用时加载// RTLD_NOW: 立即加载// 成功返回动态库的句柄,失败返回NULL,// 该函数所返回的动态库句柄唯一地标识了系统内核所维护的动态库对象,将作为后续函数调用的参数void* dlopen(const char* filename, int flag);// 获得函数或变量(全局变量)// handle: 动态库句柄// symbol: 符号(函数或全局变量)名,// 成功返回函数地址,失败返回NULL// 改函数返回的函数指针是void*, 需要通过强制类型转换,得到实际的函数指针类型才能调用void* dlsym(void* handle, const char* symbol);// 关闭动态库// handle 动态库句柄// 成功返回0, 失败返回非零int dlclose(void *handle);// 之前调用函数有错误发生,返回错误信息字符串,否则返回NULL。char* dlerror(void);
辅助工具
查看符号表:nm
- 列出目标文件(.o)、可执行文件、静态库文件(.a)或动态库文件(.so)中的符号
显示二进制模块的反汇编信息
objdump -S删除符号表和调试信息
最终发布时使用strip查看依赖的动态库
查看可执行文件或动态库依赖的动态库,只能查看静态加载ldd
错误
- 通过函数返回值表达错误
- 返回整数的函数:通过返回合法值域以外的值表示错误
- 返回指针的函数:通过返回空指针表示错误
- 不需要通过返回值输出错误信息,增加返回值,返回-1错误,返回0成功
错误号和错误信息
#include <errno.h>// 全局变量,整数,标识最近一次系统调用的错误的错误号errno#include <string.h>// 传入错误号,返回错误信息char* strerror(int errnum);#include <stdio.h>// 打印最近一次的错误信息, s 自定义提示信息void perror(const char* s);print的 "%m" 将会被替换成最近一次的错误信息
- errno:虽然是有的错误号都为0,但因为函数执行成功的情况下错误号全局变量errno不会被清0,因此不能用errno是否为0作为函数成功失败的判断条件,是否出错还是应该根据函数的返回值来决定。
环境变量
- 每个进程都有一张独立的环境变量表 ```c // 全局变量 environ
int main(int argc, char argv[], char envp[]) { extern char** environ; // environ 和 envp 相同 }
// 通过环境变量名获取环境变量值,失败返回NULL char getenv(char const name); cahr* s = getenv(“HOME”);
// 添加或修改环境变量, 成功返回0, 失败返回-1 // string 值为 “key=value” 的字符串 int putenv(char* string); putenv(“NAME=abc”);
// 添加或修改环境变量 // name 环境变量名,即等号左边的部 // value - 环境变量值,即等号右边的部分 // overwrite - 环境变量存在时,0保持该变量的原值不变,非0,则将该变量的值修改为value int setenv(const char name, const char value, int overwrite);
// 删除指定环境变量 // name 环境变量 int unsetenv(const char* name)
// 清空环境变量,成功返回0,失败返回-1 int clearenv(void)
<a name="DGtK3"></a># 内存- **虚拟内存**:地址空间,虚拟的存储区域,应用程序所访问的都是虚拟内存- **物理内存**:存储空间,实际的存储区域,只有系统内核可以访问物理内存虚拟内存和物理内存之间有对应关系,当应用程序访问虚拟内存时,系统内存会根据这种对应关系找到与之相应的物理内存。这对应关系就是**内存映射表**物理内存分为:- 半导体内存- 换页文件页面换出:当半导体内存不够用时,可以把一些长期闲置的代码和数据从半导体内存中缓存到换页文件中<br />页面换入:一旦需要使用被换出的代码和数据,再把它们从换页文件恢复到半导体内存中<br />系统中的虚拟内存比半导体内存大得多。<a name="S4eIg"></a>## 进程映射 Process Maps- 每个进程都拥有独立的4G字节的虚拟内存,分别被映射到不同的物理内存区域- 内存映射和换入换出都是以页为单位,1页 = 4096字节- **4G** 虚拟内存中**高地址**的**1G**被映射到内核的代码和数据区,这1个G在各个**进程间共享**。- 用户的应用程序只能直接访用户空间的数据,如果要想访问内核空间中的代码和数据必须借助专门的系统调用完成。<a name="spyIs"></a>## 内存分配与释放<a name="em0NA"></a>### C标准库```c#include <stdlib.h>void *malloc(size_t size);void free(void *ptr);// nmemb 元素个数,size 元素大小, 等同 malloc(nmemb * size)void *calloc(size_t nmemb, size_t size);// 扩大或缩小 ptr 指向的空间void *realloc(void *ptr, size_t size);void *reallocarray(void *ptr, size_t nmemb, size_t size);
- Unix中调用sbrk/brk函数
Unix标准库
#include <unistd.h>// increment : 大于0 堆顶指针上移,增大堆空间,分配虚拟内存// 小于0 堆顶指针下移,缩小堆空间,释放虚拟内存// 返回调用函数前的堆顶指针// 系统内核维护一个指针(指向堆内存的顶端)即有效堆内存中最后一个自己的下一个位置void *sbrk(intptr_t increment);int brk(void *addr);
内存映射
#include <sys/mman.h>void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
