操作系统-系统环境

一、UNIX系统介绍

​ 诞生于1971年美国AT&T公司的贝尔实验室,主要开发者是丹尼斯.里奇、肯.汤普逊。
​ 该系统的主要特点支持多用户、多任务,并支持多种处理器架构,同时具有高安全性、高可靠性、高稳定性,既可以构建大型关键业务系统的商业服务器,也可以构建面向移动终端、手持设备、可穿戴设备的嵌入式应用。

二、Linux系统介绍

​ 是一款类UNIX系统,免费开源,不同的发行版使用相同的内核,一般用在手机、平板、路由器、台式计算机、大型计算机、超级计算机,从严格意义上来说,Linux仅指的是操作系统内核,隶属于GNU工程,发明人叫 Linus Benedict Torvalds,1991年第一次公开在校内网的FTP服务器上。

​ 是一只企鹅,是南极的标志性动物,而目前南极不属于任何国家,为全人类所共有的,而Linux用它来当操作系统就意味这款系统属于全人类。

Minix操作系统:

​ 荷兰的Andrew S. Tanenbaum教授所开发的一款不包含任何UNIX源码的类UNIX系统,Linus Torvalds深受Minix的启发写出了第一版本的Linux内核。

GNU工程:

​ 发起于1984年,由自由软件基金会提供支持,它基本原则就是共享,目的是发展出一个免费且开源的类UNIX系统,名称来自GNU's Not UNIX!的递归缩写,因为GNU的设计类似UNIX,但它不包含具著作权的Unix代码。GNU的创始人,理查德·马修·斯托曼,将GNU视为“达成社会目的技术方法”。

​ GNU的发展仍未完成,其中最大的问题是具有完备功能的内核尚未被开发成功。GNU的内核,称为Hurd,但是其发展尚未成熟。在实际使用上,多半使用Linux内核、FreeBSD等替代方案,作为系统核心,其中主要的操作系统是Linux的发行版。Linux操作系统包涵了Linux内核与其他自由软件项目中的GNU组件和软件,可以被称为GNU/Linux。

POSIX标准:

可移植操作系统接口(Portable Operating System Interface,缩写为POSIX)是IEEE为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称。

​ Linux完全遵循了这个标准,所以两个操作系统的API,名字相同、参数相同、返回值相同,在Linux下编写的代码,经过稍微修改可移植到UNIX上。

GPL通用公共许可证:

​ GNU通用公共许可证简称为GPL,是由自由软件基金会发行的用于计算机软件的协议证书,使用该证书的软件被称为自由软件。允许对某成果及其派生成果的重用、修改和复制,对所有人都是自由的,但不能声明做了原始工作,或声明由他人所做。

Linux发行版:

​ Linux只是内核,内核+Shell+基础软件才是可用的操作系统。

​ 其它公司可以根据Linux内核制作出不同版的Linux系统。

​ ubuntu、redhat、CentOS、debian、UOS

三、GNU编译工具

​ 是GNU组织为了编译Linux内核源码而开发的一款编译工具,经过长时间的发展目前已经成为一个编译平台,能够支持多种编程语言、能够在主流操作系统中使用,编译C代码的工具gcc,编译C++代码的工具g++

​ 通过 工具名 -v 参看编译工具的版本信息

gcc常用的编译参数:

参数选项 作用 eg
-E 预处理
-S 生成汇编文件
-c 生成目标文件
-o 设置编译结果的名字 -ostl == -o stl
-I 指定要导入的头文件的路径 -I /path
-l 指定要导入的库文件 -lm导入数据库
-L 指定要链接的共享库的路径
-D 编译时定义宏 -D宏名
-g 编译时添加调试信息,这样的编译结果可以通过gdb调试
-Wall 尽可能多的产生警告,编译器更严格地检查代码
-Werror 把警告当做错误处理
-std 指定编译器遵循的语法标准 gnu89`gnu99` -std=c++2a
-pendantic 对一些不符合ANSI\ 标准的代码、扩展代码会产生警告 case a ... b:

gcc相关的文件类型:

文件 类型
xxx.c 源文件
xxx.h 头文件
xxx.i 预处理文件
xxx.s 汇编文件
xxx.o 目标文件
xxx.h.gch 头文件的编译结果,用于检查头文件语法,必须立即删除
libxxx.a 静态库文件 Windows中以.lib结尾
libxxx.so 动态库文件\共享库文件 Windows中以.dll结尾

gcc把C语言变成可执行程序的过程:

  1. 预处理
    gcc -E xxx.c 把预处理结果显示到屏幕上
    gcc -E xxx.c -o xxx.i 生成.i结尾的预处理文件
  2. 把预处理文件 编译成汇编文件
    gcc -S xxx.i 生成.s结尾的汇编文件
  3. 把汇编文件翻译成二进制的目标文件
    gcc -c xxx.s 生成.o结尾的目标文件
  4. 把若干个目标文件、库文件合并生成可执行文件
    gcc a.o b.o c.o ... 默认生成a.out可执行文件

gcc支持的预处理指令:

指令 作用
#include
#define
#undef
#if
#elif
#ifdef
#ifndef
#endif
#wanring 在预处理时产生警告信息
#error 在预处理时产生错误,并阻止可执行文件生成
#line 指定行数
#pragma pack(1\2\4\8) 设置按1\2\4\8字节数进行内存对齐、补齐
#pragma once 相当于头文件卫士
#pragma GCC posion <标识符> 禁用标识符
#pragma GCC dependency "文件名" 能够监控文件,如果该文件比本文件更新则产生警告

gcc预定义的宏:

作用
__FILE__ 返回当前文件名
__func__ 返回当前函数名
__FUNCTION__ 返回当前函数名,与编译器有关
__LINE__ 返回行数
__DATE__ 返回 月 日 年
__TIME__ 返回当天时间
__cplusplus 返回编译器版本

四、环境变量

  • 环境变量一般是指在操作系统中用来指定操作系统运行环境的一些数值参数,例如:系统头文件的加载位置、临时文件位置等
  • 环境变量是在操作系统中具有一个特定名字的对象,它里面包含了一个程序或者多个程序所需要用到的信息。例如操作系统中有path环境变量,告诉应用程序执行可执行文件时,系统会去到哪个路径查找
  • 用户可以通过设置、获取环境变量、更好地处理运行程序。

环境变量的主要作用:

  1. 设置程序的运行参数:环境变量相当于给系统、用户、程序设置一些重要的参数,具体起到什么作用与该环境变量相关,例如:设置文件查找路径、用户名信息、文件存储路径等
  2. 程序共用:比如A软件需要用到B软件的部分功能,但是无论是操作系统、A软件、B软件的作者无法决定它们安装在什么位置,但是A软件需要在开发的时候提供使用B软件的部分功能的功能,这时候可以让B软件设置新的环境变量告诉操作系统它安装在什么路径下,此后A软件就可以通过查询公共的环境变量表得知B软件安装的位置,从而调用B软件的功能
  3. 系统运行:用户还可以通过设置环境变量告诉操作系统一些特定的运行数值,例如:当前系统的语言、字符编码、终端默认的窗口大小、字体大小等

常见的环境变量:

PS1			#	命令提示符
PATH		#	命令程序的搜索路径
C_INCLUDE_PATH	#	标准库头文件的搜索路径
LIBRARY_PATH	#	库文件的搜索路径
LD_LIBRARY_PATH	#	程序执行时动态库的链接路径

查看环境变量:

  • Linux中通过env命令查看当前用户的环境变量
  • Windows中通过set命令查看环境变量
  • 在程序中查看环境变量
    • 在程序中查看环境变量之前,需要先通过声明

每个程序运行时,操作系统都会拷贝系统的环境变量表给该程序,名字为environ,该表末尾一定是以空指针结尾,是一个字符串数组

int main(int argc, const char *argv[], const char *environ[]) {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    for (int i = 0; environ != nullptr; ++i) {
        std::cout << environ[i] << '\n';
    }
}

修改环境变量:

  1. Linux系统修改
  1. 打开Linux系统配置文件
    vim ~/.bashrc 只对当前用户有效
    vim /etc/environment 对所有用户有效
  2. 在文件末尾追加内容,对环境变量进行增删改查
    export 环境变量名=<环境变量的值>
    exprot 环境变量名=$环境变量名:<追加的环境变量值>
  3. 保存退出后,重新加载配置文件
    source ~/.bashrc
  1. 通过使用标准库函数设置环境变量
    环境变量的格式name = value
/**
 * 功能: 获取env_var环境变量的值
 * @env_var: 标识要查找的环境变量名的空终止字符串 
 * 返回值: 标识环境变量值的字符串,或若找不到这种字符串则为空指针。
*/
char* getenv(const char *env_var);

/**
 * 功能:以name = value格式设置环境变量的值,如果name不存在则直接添加,如果存在则覆盖原来的value
 * 返回值:成功返回0,失败-1
*/
int putenv(char *string);

/**
 * 功能:通过value值给name环境变量设置,如果不存在也是添加,如果存在是否覆盖要根据overwrite决定
 * @overwrite: 当name存在时有意义0/1(不覆盖/覆盖)
*/
int setenv(const char *name, const char *value, int overwrite);

/**
 * 功能:删除环境变量name
*/
int unsetenv(const char *name);

/**
 * 功能:清空当前程序的环境变量表
*/
int clearenv(void);
  • 注意:实际上是把char** environ指针置空当做清空处理,所以使用environ之前需要先判断是否是空指针
  • 注意:由于当前程序获得的环境变量表是系统拷贝过来的(副本),因此对其所有操作仅仅是本程序本次运行有效,不会对其它程序造成影响,但是会对子进程有影响

五、错误处理

  1. 通过函数的返回值表示执行错误:
  2. 通过errno全局变量表示错误:
    • errno是一个记录系统最后一次错误代码原因的全局变量,是int类型的值,需要导入头文件<errno.h>通过该全局变量的值查看和调试程序错误。
    • 当调用Linux系统API函数发生异常,一般系统会自动地将本程序的errno修改一个值,不同的值表示不同的异常,可以通过该值获取和推测出程序出现了什么问题,实际编程中能找出大部分调用系统API出现的异常
    • 一般errno在程序执行成功时不会被修改,但是毕竟是一个全局变量,可能被其他人或者操作失误而修改,因此不能以errno非零就断定程序出现了异常,只能先根据其他具体条件判断出程序出现异常后,才能使用errno来确定异常的原因

六、库文件的制作与使用

库文件

  • 库文件是计算机中的一类文件统称,提供给开发者一些开箱即用的变量、函数、类,是若干个目标文件的集合,这样可以既可以保护源码,同时也对源码的使用提供了方便管理、使用、安全性高
  • 库文件分成静态库、动态库,区别具体在于程序的

静态库与动态库(共享库)的区别

  • 静态库在程序链接之前就已经复制到了程序中,一起形成一个可执行文件,后续链接和加载到内存就不需要静态库文件参与;动态库在链接之前没有复制,而是在程序运行时,一起由系统加载到内存中,当执行到动态库中的语句时,内存发生跳转到动态库的代码段执行,执行结束后跳转回程序的代码段往下执行;这是这两个文件的最本质的区别

静态库的制作与使用:

创建静态库

# 编译出要打包的目标文件:
gcc -c xxx1.c
gcc -c xxx2.c
...
    
# 	把目标文件合并打包成静态库文件
#	静态库一定是 前缀lib + 库名xxx +后缀.a
ar -r libxxx.a xxx1.o xxx2.o ...
#	ar 是一个专门控制静态库的命令集合

控制静态库的指令

指令 作用
-r 把目标文件合并成一个静态库文件,如果已有静态库则会更新
-q 向静态库中添加目标文件
-t 查看静态库中有哪些目标文件
-d 从静态库中删除目标文件
-x 把静态库展开成目标文件

使用静态库
方法1: 直接使用,相当于把库文件当做目标文件一样使用,意义不大。

gcc main.c libxxx.a

方法2:通过设置LIBRARY_PATH环境变量的值来指定库文件的查找路径
前提是需要将静态库放入自己指定的静态库查找路径中

vim ~/.bashrc # 1、打开Linux系统配置文件		
exprot LIBRARY_PATH=$LIBRARY_PATH:要指定的路径 # 2、在文件末尾追加内容
source ~/.bashrc # 3、保存退出后,重新加载配置文件
gcc main.c -lxxx  # 4、通过-l库名 指定要使用的库

方法3:通过-l库名, 指定使用当前工作路径下的库

gcc main.c -lxxx	#注意:libxxx.a必须在当前路径下或者系统指定路径

方法4:通过-L路径,去该路径查找库,找不到继续从当前目录、系统指定目录找

gcc main.c -lxxx -Lpath

注意:如果要删除系统配置文件中的环境变量,需要重启系统,才会生效

动态库的制作与使用:

创建动态库文件

# 编译出目标文件,-fpic  编译出与位置无关的代码
gcc -c -fpic xxx1.c
gcc -c -fpic xxx2.c
...
# 把目标文件打包合并成有执行权限的动态库文件libxxx.so
gcc -shared xxx1.o xxx2.o ... -o libxxx.so

使用动态库
方法1:直接调用

gcc main.c libxxx.so	#	只要当前路径下有so就可以生成a.out
a.out	#	可能报错 默认下不会在当前路径加载so 只会去默认路径/lib

方法2:通过设置LIBRARY_PATH环境变量的值来指定库文件的查找路径,前提是需要将静态库放入自己指定的静态库查找路径中

vim ~/.bashrc # 1、打开Linux系统配置文件
exprot LIBRARY_PATH=$LIBRARY_PATH:要指定的路径 # 2、在文件末尾追加内容
source ~/.bashrc # 3、保存退出后,重新加载配置文件
gcc main.c -lxxx # 通过-l库名 指定要使用的库

方法3:通过-l库名,指定使用当前工作路径下的库

gcc main.c -lxxx # libxxx.so 必须在当前路径下或者系统指定路径

方法4:通过-L路径,去该路径查找库,找不到继续从当前目录、系统指定目录找

gcc main.c -lxxx -Lpath

注意:如果无法执行a.out, 需要检查系统能否在正确的路径下加载对应的动态库文件,检查LD_LIBRARY_PATH环境变量的值
注意:在指定路径中,存在同名的静态库libxxx.a和动态库文件libxxx.so时,系统默认优先使用动态库文件,也可以通过参数 -static 指定

*静态库与动态库的优缺点对比

静态库优点:
  1. 使用方便:
    • 在与静态库文件编译生成可执行文件后,编译器会把静态库中的内容一起编译到可执行文件中,在后续的执行程序时不需要依赖静态库文件了,并且可执行文件可以跨平台、跨设备直接运行
  2. 运行速度快:
    • 因为静态库文件是拷贝到可执行文件中,所以程序运行时不发生内存跳转,运行速度比动态库更快
静态库缺点:
  1. 浪费内存:
    • 假如有一个静态库libxxx.a,以及多个程序a.out b.out c.out都是用该静态库,那么libxxx.a的内容会分别给它们各拷贝一份,如果三个程序同时运行,静态库就会在内存中存在同样的三份,内存冗余,并且相比于动态库的可执行文件而言,静态库的可执行文件更大
  2. 更新麻烦:
    • 如果静态库的内容发生了改变:版本更迭、修改BUG等,那么所有使用过该静态库的可执行文件都需要重新编译,会耗费大量时间。
动态库的优点:
  1. 节约内存:

    • 所有使用同一个动态库的可执行文件,它们的动态库只会加载到内存一次,不同的程序可以使用那一次加载到内存的动态库内容,所以能够节约内存,这也是动态库也称为“共享库”的原因
  2. 更新方便:

    • 当动态库的内容更新,但是函数名、参数格式、返回值这些没改动的前提下,只需要重新编译动态库文件即可,直接运行原来的可执行文件,系统会加载最新版的动态库文件,不需要全部重新编译,节约时间
使用库文件的辅助工具:
nm <filename> # 查看目标文件、可执行文件、静态库、共享库文件的符号列表
strip <filename> # 删除目标文件、可执行文件、静态库、共享库文件的符号列表、调试信息,有效降低文件大小
ldd <filename> # 查看可执行文件依赖于哪些动态库
objdump <参数选项> <filename> # 把二进制转换成汇编,就是反汇编
posted @ 2024-08-16 20:41  sleeeeeping  阅读(50)  评论(0)    收藏  举报