达内C语言UnixC(DAY02)

  1. 动态库(共享库)
  1. 动态库和静态库最大的不同就是,链接动态库并不需要将库中的被调用代码复制到调用模块中,
    相反被嵌入到调用模块中的仅仅是被用代码在动态库中的相对地址。
    在调用模块实际运行时,再根据动态库的加载地址和被调用代码的相对地址去计算出该代码的绝对地址
    读取代码的内容,并运行之。
    如果动态库中的代码同时被多个进程所用,动态库的实例在内存中仅需一份,因此动态库也叫共享库。
    使用动态库占用的内存空间小,即使修改了动态库中的代码,只要其相对地址不变,无需重新链接。
    因为在执行过程中,需要计算被调用代码的绝对地址,以及一些附带的额外开销,所以调用动态库会
    比调用静态库略慢一些
  2. 动态库形式:libxxx.so
  3. 构建动态库:
    编译选项:-fpic,位置无关代码
    链接选项:-shared,没有main函数的可执行程序
  4. 链接动态库
    gcc … lxxx -L<库路径> …
    gcc … lxxx <—— LIBRARY_PATH
  5. 调用动态库:运行时需要调用动态库中的代码,因此动态库必须位于LD_LIBRARY_PATH环境变量所表示的某个路径下
    代码:shared/
    因为相较于静态库而言,动态库具有明显的优势,因此gcc的链接器缺省使用动态库版本
    如果一定要使用静态库版本参与链接,需要加上-static链接选项。
    代码:hello.c
  1. 动态库的动态加载
    Linux操作系统提供的一个用于动态加载动态库的动态库:dl
    #include <dlfcn.h>
    -ldl

    1. 加载动态库
      void* dlopen(const char* filename, int Flag);
      成功会返回一个动态库句柄(handle),失败返回NULL。
      filename:动态库路径,也可以只给文件名,这中情况下函数将根据LD_LIBRARY_PATH环境变量中的路径搜索该动态库
      flag:加载方式,可取以下值:
      RTLD_LAZY —— 延迟加载
      RTLD_NUW —— 立即加载
      该函数所成功返回的动态库句柄唯一地表示系统内核为维护的动态库实例,将作为后续函数调用的参数
    2. 获取符号(函数名或者变量名)地址
      void* dlsym(void* handle, const char* symbol);
      成功会返回库中指定函数或变量(全局)的地址,失败返回NULL
      handle:动态库句柄
      symbol:符号名,即函数名或变量名
      该函数返回的指针类型为void*,需要显示转换为实际的指针类型才能访问其具体目标。可见所获取符号的实际类型必须
      事先知道 3) 卸载动态库 int dlclose(void* handle); 成功返回0,失败返回非零 handle:动态库句柄 所卸载的动态库未必真的会从内存中立即消失,因为其他程序可能还需要使用该库。 dlopen(…) ——> handle1 dlopen(…) ——> handle2 dlopen(…) ——> handle3 dlclose(handle2) dlclose(handle3) dlclose(handle1) 动态库实例 引用计数:0 ——> 释放内存 该函数一方面会解除参数句柄和动态库实例之间的关联,使该句柄失效,另一方面会将动态实例中的引用计数减1,当其 被减到0时才会真的释放所占内存资源 4) 获取错误信息 char* dlerror(void); 有错误发生则返回指向错误信息字符串的指针,否则返回NULL 代码:load.c 5. 辅助工具 1) 查看符号表 nm 目标模块/可执行程序/静态库/动态库 列出目标模块/可执行程序/静态库/动态库中的符号(函数或全局变量) T:正文段,函数的代码块,地址 U:正文段,函数的调用,其代码块位于某个动态库中,无地址 D:数据段,全局变量 … 2) 反汇编 objdump -S 目标模块/可执行程序/静态库/动态库 将目标模块/可执行程序/静态库/动态库中的二进制形式的机器码转换为字符形式的汇编代码,打印在屏幕上 3) 消除冗余信息 strip 目标模块/可执行程序/静态库/动态库 去除目标模块/可执行程序/静态库/动态库中的符号表,调试信息等非运行所必须的冗余信息,以缩减文件的大小 4) 查看动态库依赖 ldd 可执行程序/动态库 显示可执行程序或动态库所依赖的动态库信息 三. 错误处理 FILE* fp = fopen(…); fread(…, fp);
      处理数据
      fwrite(…,fp);
      fclose(fp);
      当错误发生时:逃跑、 提示再逃跑、 自动纠错。
      /
      是否出错了? 出了什么错?
      | |
      返回值 错误号
      \ /
      查询手册页
    1. 判断一个函数是否出错
      1)返回指针函数,如:malloc,fopen,dlopen等,通常用返回NULL表示失败。
      2)返回值属于特定值域的函数,通常会用返回合法值域以外的值表示失败
      3)用返回0来表示成功,返回-1或其他非零值来表示失败,如果有数据输出,则通过指针型参数向调用者来输出数 据

    2. 标准库中预定义了全局变量errno,表示最近一次函数调用的错误编号
      #include <errno.h> // 声明errno、 定义错误号宏
      FILE* fp = fopen(…);
      if(!fp) {
      if(errno == EINUAL)
      处理无效读写模式错误
      else if (errno == …)
      处理…错误

      }

    3. 将整数形式的错误号转换为字符串
      #include <string.h>
      char* strerror(int errnum);
      返回与参数错误号相对于的错误描述字符串指针
      #include <stdio.h>
      void perror(const char* s);
      将最近一次函数调用的错误描述字符串打印到标准错误设备上。
      perror(“abcde”); //abcde:错误描述字符串
      %m格式化标志——> 错误描述字符串
      代码:errno.c
      注意:函数在出错时会将一个大于零的整数作为错误号存放在全局变量errno中,如果该函数执行成功,通常不会 将errno赋0,因此不能用errno是否为0作为函数成功失败的判断一句,而是根据函数的返回值判断其成功 或失败,只有在确定失败的前提下,才能根据errno判断句具体的失败原因
      代码:iferr.c
      四. 环境变量

    4. 环境变量表
      每个进程在系统内核中都有一个数据结构维护与该进程有关的审计信息,称为进程表项,其中就包含了一张转属于该进程的环境变量表。环境变量表其实就是一个以NULL指针结尾的字符指针数组,其中每个字符指针类型的数组元素都指向一个以空字符(’\0’)结尾的字符串。
      该字符串形如:变量名 = 变量值,即一个环境变量。
      进程1
      进程2 <----------------------------------+

      用户空间
       							内核空间  |
      

      进程表项 |
      进程表项1 |
      进程表项2 <---------------------------+
      各种ID
      启动时间、 运行时间、 启动命令、 优先级、等等
      文件描述符表
      环境变量表
      environ/envp ——> * ——> PATH=/bin:/usr/bin:…\0
      \ / * ——> SHELL=/bin/bash\0
      char** …
      NULL


      int main(int argc, char* argv[], char* envp[]) { … }

    5. 访问调用进程的环境变量

      1. 根据一个环境变量的名称来获取其值
        cahr* getenv(const cahr* name);
        成功返回与给定环境变量名匹配的环境变量值,失败返回NULL。
        name ——> 环境变量名
      2. 修改或新增环境变量 // name和value一起
        int putenv(char* name_value);
        成功返回0,失败返回非零
        name_value ——> 形如"变量名=变量值"的字符串,若变量名不存在则新增该环境变量,
        若变量名已存在,则修改其值。
        int setenv(const char* name, const char* value, int overwrite);
        成功返回0,失败返回-1
        name ——> 环境变量名。若该变量名不存在,则新增该环境变量,若以存在则根据
        overwrite的值修改或保留原值。
        value ——> 环境变量值
        overwrite ——> 当name参数所表示的环境变量名已存在时,是否用value来覆盖原
        来的变量值。非零则覆盖,零则不覆盖。
      3. 根据名称删除环境变量
        int unsetenv(const char* name);
        成功返回0,失败返回-1.
      4. 情况整个环境变量表
        int clearenv(void);
        成功返回0,失败返回非零

      一旦环境变量表被情空,全局变量environ即成为NULL指针

    代码:env.c

五.内存
应用程序:根据业务逻辑选择合适的数据结构
STL:容器、 迭代器、 内存分配器 用户层
C++:new、 delete、 delete[]
C: malloc、 calloc、 realloc、 free

  POSIX:sbrk、 brk						 系统层
  Linux:mmap、 munmap 					 

  系统内核:kmalloc、 vmalloc				 内核层
  硬件驱动:get_free_page
      硬件:指令集、 电路
1. 进程映像
	程序是由可执行代码和全局数据组成的磁盘文件。运行程序时,需要将磁盘上的
	可执行程序文件加载到内存中,以使处理器可以执行其中的代码,处理其中的数据,形成进程。
	进程在内存空间中的布局形成进程映像,,从低地址到高地址依次为:

------------------------------------------------------- 高地址
命令行参数表和环境变量表的指针

				栈区
			非静态局部变量

					|


					|

				堆区
			动态内存分配

				BSS区
	不带常属性且未初始化的全局变量和静态局部变量

				数据区
	不带常属性且被初始化的全局变量和静态局部变量

			代码区(只读)
			可执行指令
			字面值常量
	带有常属性且被初始化的全局和静态局部变量

--------------------------------------------------------- 低地址

参考代码:https://gitee.com/cui-qinghe/c-study-notes.git

上一篇:day02


下一篇:Day02 Dos