Linux系统开发(UC)学习笔记(上)

背景介绍

shell(壳)脚本不需要编译可以直接执行

c语言需要经过编译才可以执行

python也可以不编译,直接用python解释器解释给操作系统

硬件-内核-系统调用-壳/库函数/应用

环境变量

每个进程都有一张自己的环境变量表,表中的每个条目都是形如“键=值”形式的环境变量

值:路径

环境变量是操作系统或进程在运行时用来存储配置信息的变量。每个进程都会拥有自己的一张环境变量表,表格中的每个条目都以 键=值 形式存在。例如,PATH=/usr/bin:/bin:/usr/sbin:/sbin。

环境变量主要用于存储系统的配置信息,或者与进程间的通信相关的设置,比如操作系统路径、用户信息、配置参数等。

进程可以通过环境变量访问计算机资源

在终端下输入env命令,可以查看环境变量列表

通过echo $name可以查看某个环境变量的值

环境变量的类别:

全局环境变量:当前shell和其他子进程都是可见的

全局环境变量:

全局环境变量在当前 shell 会话及其子进程中都可见。任何通过 fork() 创建的子进程都能继承父进程的环境变量,因此它们在子进程中也是有效的。

常见的全局环境变量有 PATH、HOME、USER、SHELL 等。

局部环境变量:

局部环境变量:只有当前shell可以看见

局部环境变量:\n局部环境变量只在当前 shell 进程中可见,当你启动新的 shell 会话时,局部变量不会被继承。\n在设置局部环境变量时,它仅对当前 shell 会话有效,结束 shell 会话后它会消失。


将局部环境变量设置成全局变量:export name

删除环境变量:unset name

export MY_VAR="Global Variable"

echo $MY_VAR # 输出:Global Variable

unset MY_VAR

echo $MY_VAR # 输出为空,因为 MY_VAR 被删除了


路径记录的是bash对文件的检索路径:

就像运行程序./a.out也是告诉bash去哪里运行可执行程序,因为bash本身有固定的路径,需要往里添加该路径,或者直接给出路径。 直接把之前的bash路径复制加一个:.代表添加了当前路径可以一劳永逸


whereis ls:找到各种路径下的ls


在PATH前加一个$相当于PATH的值(即路径内容)

PATH=$PATH:.

:用来将路径间做分隔

bash是命令行窗口,bash是个进程,关掉窗口,进程结束


如果没有特殊操作,对环境变量的设置仅对当前shell进程有效,开启新的终端,之前的操作不会被保留

在家目录下有名为.bashrd的脚本文件,每次bash进程启动前,都会执行该脚本文件的内容。如果希望环境变量的设置对每个bash进程都有效,可以将环境变量(PATH=$PATH:.)的设置写

在(vi)该脚本中执行source~/.bashrc,可以使文件立即生效

环境变量表

每个进程都有一张独立的环境变量表,其中每个条目都是一个形如“键=值”形式的环境变量

extern char **environ; 是 C 语言中用于访问环境变量的一个特殊声明。它与程序运行时的环境变量相关,允许我们在程序中访问和操作进程的环境变量表。

#include <stdio.h>

extern char **environ; // 声明 environ 变量

int main() {

// 遍历环境变量并打印

for (char **env = environ; *env != NULL; env++) {

printf("%s\n", *env); // 输出环境变量

}

return 0;

}

库文件、静态库、动态库

库文件

windows系统下的插件就是动态库

分为动态库和静态库

单一模型:将程序中所有的功能全部实现与一个单一的原文件内部。编译时间长,不易于维护和升级,不易于协作开发。

分离模型:不同功能分到不同的源文件中

将多个目标文件(.o)统一整理合成一个库文件


静态库的本质就是将多个目标文件(不同功能的)打包成一个文件

链接静态库的就是将库中被调用的代码复制到调用模块(需要使用的地方)中

静态库的拓展名是.a libxxx.a

静态库(Static Library)

静态库将程序需要的代码直接集成到最终的可执行文件中。当编译程序时,静态库中的代码被复制到程序中去。静态库的优点是它不需要在运行时依赖外部的库文件,因此在程序分发时,通常不需要单独传送库文件。

静态库的特点:

文件扩展名:通常为 .a(在 Linux 下)或 .lib(在 Windows 下)。

链接方式:在编译时,静态库会被链接到程序中,程序的最终可执行文件会包含库中的所有代码。换句话说,静态库中的函数在编译时被复制到目标文件中。

优点:

程序运行时不需要依赖外部库文件。

部署更简单,不依赖外部环境。

缺点:

静态库在编译时就已被链接到可执行文件中,修改库文件时必须重新编译程序。

增加了可执行文件的大小。


静态库构建:

1.编辑库的实现代码和接口声明 计算模块:calc.h、calc.c 显示模块:show.h、show.c 接口文件:math.h

2.编译成目标文件 gcc -c calc.c gcc -c show.c

3.打包成静态库 ar -r libmath.a calc.o show.o

写main时候要写#include“XXX.h”

编译的时候把库加上: gcc main.c libmath.a

4.编译并连接静态库 直接连接静态库(如上)

如果库和文件不在一个位置: 用-l指定库名,用-L指定库路径 gcc main.c -lmath -L. 用-l指定库名,用LIBRARY_PATH环境变量指定库路径 export LIBRARY_PATH=$LIBRARY_PATH:.(原来的基础上追加路径) gcc main.c -lmath (提前声明环境变量就不需要给库的地址了)


可以把所有的头文件include到接口文件中,库 包含接口文件即可。

快捷键:__ctrl n 找之前用过的变量名

👈库和调用文件要在一个目录才能同时gcc,需要加参数


静态库的创建步骤:

编写实现代码和接口声明:

比如,calc.h, calc.c, show.h, show.c,这些文件实现了库的功能。

接口文件通常是math.h,用来提供对外暴露的函数声明。

编译成目标文件:

编译源代码文件为目标文件(.o 或 .obj 文件)。

bash

gcc -c calc.c

gcc -c show.c

打包成静态库:

使用 ar 命令将目标文件打包成一个静态库文件(.a)。

bash

ar r libmath.a calc.o show.o

编译并连接静态库:

在编译应用程序时,使用 -l 参数指定库名,使用 -L 参数指定库路径。

bash

gcc main.c -L. -lmath

其中:

-L. 用来指定库文件的路径。

-lmath 表示连接 libmath.a 静态库。

另外,可以通过 LIBRARY_PATH 环境变量来指定库的搜索路径,避免在每次编译时指定路径:

bash

export LIBRARY_PATH=$LIBRARY_PATH:/path/to/libs

gcc main.c -lmath

头文件保护:

为了避免头文件重复包含,通常使用预处理指令:

#ifndef CALC_H

#define CALC_H

// 头文件内容

#endif


制作库:写到.C文件,得到.o文件,通过.o得到库.a


头文件卫士:(防止头文件被重复包含)

#ifndef CALC_H

#define CALC_H

#endif


两个文件同时定义int a一起编译容易报错,加extern代表用的是另一个文件的a


动态库:不会把用到的代码做一份拷贝,而是会嵌入一段指令,根据指令去库里找函数 ,然后执行

动态库的构建:

1.编辑库的实现代码和接口声明

计算模块:calc.h、calc.c

显示模块:show.h、show.c

接口文件:math.h

2.编译成目 标文件

gcc -c -fpic calc.c

gcc -c -fpic show.c

3.打包成动态库

gcc -shared calc.o show.o -o libmath.so

4.编译时,同样一起编译

需要给一个环境变量,让连接器知道库的位置:LD_LIBRARY_PATH=$LD_LIBRARY_PATH: . 变全局export LD_LIBRARY_PATH


LD_LIBRARY_PATH:连接器a.out执行过程中找库

LIBRARY_PATH:gcc编译器找库

PATH:bash用,找命令


动态库的创建步骤:

编写实现代码和接口声明:

同静态库一样,先编写源代码并声明接口。

编译成目标文件:

使用 -fpic 标志来编译为位置无关代码(Position Independent Code),这是生成动态库时的必要步骤。

bash

gcc -c -fpic calc.c

gcc -c -fpic show.c

打包成动态库:

使用 -shared 参数将目标文件打包成动态库(.so)。

bash

gcc -shared calc.o show.o -o libmath.so

编译并链接时使用动态库:

在编译程序时,链接器会在运行时找到动态库。

bash

gcc main.c -L. -lmath

设置库路径:程序运行时,动态库需要通过 LD_LIBRARY_PATH 环境变量来找到。

bash

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/libs

这将告诉操作系统去哪里查找动态库。

动态库的更新:

动态库可以随时更新,只要更新 .so 文件,程序在运行时会加载最新的库,无需重新编译程序。


动态库想要更新直接更新库就可以,程序会找到新的并使用,但是静态库因为之前的已经复制到程序中了,因此需要重新编译才能更新;

动态库的特点:

文件扩展名:在 Linux 上通常为 .so(共享库),在 Windows 上为 .dll(动态链接库)。

链接方式:程序在编译时并不将库代码复制到程序中,而是在运行时通过操作系统加载共享库,并在需要时调用相应的函数。

优点:

动态库可以被多个程序共享,减少了内存占用。

更新动态库时,不需要重新编译程序,程序会自动使用最新版本的库。

缺点:

需要确保动态库在运行时可用。

程序和库之间的接口需要保持兼容性,否则可能出现错误。


LD_LIBRARY_PATH: 连接器a.out执行过程中找库

LIBRARY_PATH: GCC编译阶段找库

PATH: bash找命令

动态加载、错误处理

动态库的动态加载

什么时候用什么时候加载(防止内存资源的浪费)

动态库的动态加载可以在程序运行时按需加载库文件,避免占用不必要的内存资源。你需要使用一组特定的函数来加载和卸载动态库,这些函数声明在 <dlfcn.h> 头文件中,并且需要链接 -ldl 库。


不能直接编译了,需要调用一组特殊的函数,他们被声明于一个专门的头文件中,并在一个独立的库中予以实现


使用这组函数需要包含头文件,并连接该库

#include<dlfcn.h>

-ldl(链接dl库)

函数

voidhandle=voiddlopen(char const*filename,inte flag);将共享库载入内存并获得其访问句柄(句柄就是找东西的)

参数:filename 动态库路径,只给文件名不带目录,则根据LD_LIBRARY_PATH环境变量的值搜索动态库

flag 加载方式:

(宏)RTLD_LAZY延迟加载,使用动态库中的符号时才真的加载进内存 RTLD_NOW立即加载


dlopen()

用于加载动态库,并返回一个句柄。

void* handle = dlopen(const char* filename, int flag);

filename:动态库路径,如果只给文件名,会根据 LD_LIBRARY_PATH 查找。

flag:指定加载方式,常用的标志有:

RTLD_LAZY:延迟加载,只有在需要某个符号时才加载。

RTLD_NOW:立即加载。


voiddlsym(voidhandle,char const* symbol)从已经被加载的动态库中获取特定名称的符号地址

参数:handle 动态库访问句柄

symbol 符号名

该函数所返回的指针为boid*类型,需要造型为实际目标类型相一致的指针,才能使用


dlsym()

获取加载库中某个符号(如函数或变量)的地址。

void* dlsym(void* handle, const char* symbol);

handle:由 dlopen 返回的库句柄。

symbol:符号的名称(函数名或变量名)。

返回值是 void* 类型,需要强制类型转换为实际的函数指针类型。


int dlclose(void*handle)从内存中卸载动态库

参数:handle 动态库句柄

所卸载的共享库未必会真的从内存中立即消失,因为其他程序可能还需要使用该库只有所有使用该库的程序都显示或隐式地卸载了该库,该库所占用的内存空间才会真正的释放

无论卸载的共享库是否真的释放,传递给dlclose函数的句柄都会在该函数成功返回后立即失效


dlclose()

卸载已经加载的动态库。

int dlclose(void* handle);

handle:由 dlopen 返回的库句柄。


char*dlerror(void)获取在加载、使用和卸载共享库过程中所发生的错误

stdin键盘

stdout显示器

stderr显示器(和上边的区别是不会经过输出缓冲区,直接输出到显示器上,不会漏掉信息)

dlerror()

获取最近一次加载、使用或卸载动态库时的错误信息。

char* dlerror(void);


过程:

将动态库载入内存

void *handle=void* dlopen(char constfilename,inte flag)

获取库中函数地址(函数指针形式)

int (*add)(int,int)=dlsym(handle,“add”)(意思是 add是参数为int返回值也是int的指针)

使用

int a=123,b=456

add(a,b)即可

卸载动态库

dlclose(handle)


辅助命令:

nm:可以看到库中的符号(查二进制文件内容)

ldd:可以看到依赖的库

加载动态库:

void* handle = dlopen("libmath.so", RTLD_LAZY);

if (!handle) {

fprintf(stderr, "Error loading library: %s\n", dlerror());

return -1;

}

获取库中的函数指针:

int (*add)(int, int) = dlsym(handle, "add");

if (!add) {

fprintf(stderr, "Error finding symbol: %s\n", dlerror());

dlclose(handle);

return -1;

}

使用函数:

int result = add(123, 456);

printf("Result: %d\n", result);

卸载动态库:

dlclose(handle);

错误处理:

如果一个函数的返回值是一个指针,那么就用返回NULL指针的方式表示错误,如果一个函数的合法返回属于某个有限的值域,那么就用该值域以外的值表示错误;如果一个函数不需要返回数据,那么就用0表示成功,返回-1表示失败

返回值:

当函数返回指针时,如果发生错误,返回 NULL。

当函数返回某个有限值时,使用该值域外的值来表示错误(例如,返回 -1 来表示错误)。

如果函数不返回数据,通常使用 0 表示成功,-1 表示失败。


系统定义的整数类型全局变量errno中存储了最近一次系统调用的错误编号 头文件errno.h中包含了对errno全局变量的外部声明和各种错误号的宏定义

errno:

系统调用或标准库函数发生错误时,通常会设置 errno 变量,errno 保存的是最近一次系统调用的错误代码。

errno.h 头文件提供了对 errno 的声明和常见错误码的宏定义。

#include <errno.h>

#include <stdio.h>

if (some_system_call() == -1) {

perror("Error");

}

perror() 会打印出错误信息,通常是基于 errno 的当前值。


#include<stdio>

常见的错误码:

EINVAL:无效参数。

ENOMEM:内存不足。

EIO:输入输出错误。

EBADF:无效的文件描述符。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值