【webserver 前置知识 01】Linux系统编程入门

题外话,PA里面也有很不错的Linux基础基础
传送门:https://nju-projectn.github.io/ics-pa-gitbook/ics2019/linux.html

静态库与动态库

什么玩意?

库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量函数

​ ◼ 库是特殊的一种程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行

​ ◼ 库文件有两种,静态库动态库(共享库)。

静态库与动态库的区别

复制代码到程序的时机不同

​ 静态库在程序的链接阶段被复制到了程序中;

​ 动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用

库的好处

1.代码保密

2.方便部署和分发

静态库的制作与使用

命名规则

Linux : libxxx.a(库文件的名字)
lib : 前缀(固定)
xxx : 库的名字,自己起(注意,在使用gcc编译时需要的是xxx,不含lib前缀
.a : 后缀(固定)
Windows : libxxx.lib

静态库的制作

◆ gcc 获得 .o 文件

◆将 .o 文件打包,使用 ar 工具(archive)

ar rcs libxxx.a xxx.o xxx.o

​ r –将文件插入备存文件中
​ c –建立备存文件
​ s –索引

root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# tree
.
├── add.c
├── div.c
├── head.h
├── main.c
├── mult.c
└── sub.c

0 directories, 6 files
root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# gcc -c add.c div.c mult.c sub.c 
root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# ls
add.c  div.c  head.h  mult.c  sub.c
add.o  div.o  main.c  mult.o  sub.o
root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# tree
.
├── add.c
├── add.o
├── div.c
├── div.o
├── head.h
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o

0 directories, 10 files
root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# ar rcs libcalc.a add.o sub.o mult.o div.o
root@ubuntu:/home/ag/cpp/Linux/lesson04/calc# tree
.
├── add.c
├── add.o
├── div.c
├── div.o
├── head.h
├── libcalc.a
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o

0 directories, 11 files

静态库的使用

在写项目的时候,一般会进行分包操作

一个常见的项目树如下:

root@ubuntu:/home/ag/cpp/Linux/lesson05/library# tree
.
├── include    //放头文件
│   └── head.h
├── lib        //放库文件
├── main.c     //测试文件
└── src        //源代码
    ├── add.c
    ├── div.c
    ├── mult.c
    └── sub.c

3 directories, 6 files

把之前制作的静态库文件libcalc.a复制到 lib 目录下即可,然后去编译

ps:使用库文件时,还有同时有其依赖的头文件

编译文件

root@ubuntu:/home/ag/cpp/Linux/lesson05/library# gcc main.c -o app
main.c:2:10: fatal error: head.h: No such file or directory
 #include "head.h"
          ^~~~~~~~
compilation terminated.

这里报错的原因是:编译时,需要把头文件的代码拷贝到main.c中,但是我们include的时候用的是相对路径,main.c与head.h并不在统一目录下,因此是识别不到的

解决方法1:让编译文件(main.c)与头文件(head.h)在同一目录下

解决方式2(优雅):使用gcc提供的参数选项 -I 在编译时查找指定目录下的头文件

gcc main.c -o app -I ./include

又报错

root@ubuntu:/home/ag/cpp/Linux/lesson05/library# gcc main.c -o app -I ./include
/tmp/cct81thC.o: In function `main':
main.c:(.text+0x3a): undefined reference to `add'
main.c:(.text+0x5c): undefined reference to `subtract'
main.c:(.text+0x7e): undefined reference to `multiply'
main.c:(.text+0xa0): undefined reference to `divide'
collect2: error: ld returned 1 exit status

原因:main中使用了上述函数,但是编译时只找到了 .h 中的声明,没有找到库文件中的定义

还需要追加gcc参数,使用 -l 指定使用哪个库文件(只需要库名字,前缀lib不需要)进行编译, -L 指定到某个目录下找

root@ubuntu:/home/ag/cpp/Linux/lesson05/library# gcc main.c -o app -I ./include -l calc -L ./lib
root@ubuntu:/home/ag/cpp/Linux/lesson05/library# ls
app  include  lib  main.c  src

./app可以正常执行

动态库的制作与使用

命名规则

Linux : libxxx.so(库文件的名字)
lib : 前缀(固定)
xxx : 库的名字,自己起(注意,在使用gcc编译时需要的是xxx,不含lib前缀
.so : 后缀(固定,在linux下是一个可执行文件)
Windows : libxxx.dll

动态库的制作

◆ gcc 获得 .o 文件,得到和位置无关的代码

gcc -c –fpic/-fPIC axx.c bxx.c

◆ gcc得到动态库

gcc -shared a.o b.o -o libcalc.so
root@ubuntu:/home/ag/cpp/Linux/lesson06/calc# gcc -c -fpic add.c div.c sub.c mult.c 
root@ubuntu:/home/ag/cpp/Linux/lesson06/calc# ls
add.c  add.o  div.c  div.o  head.h  main.c  mult.c  mult.o  sub.c  sub.o
root@ubuntu:/home/ag/cpp/Linux/lesson06/calc# gcc -shared add.o sub.o mult.o div.o -o libcalc.so
root@ubuntu:/home/ag/cpp/Linux/lesson06/calc# ls
add.c  div.c  head.h      main.c  mult.o  sub.o
add.o  div.o  libcalc.so  mult.c  sub.c

然后把 .so 拷贝到要使用的地方

root@ubuntu:/home/ag/cpp/Linux/lesson06/calc# cd ..
root@ubuntu:/home/ag/cpp/Linux/lesson06# cd library/
root@ubuntu:/home/ag/cpp/Linux/lesson06/library# cp ../calc/libcalc.so ./lib/
root@ubuntu:/home/ag/cpp/Linux/lesson06/library# tree
.
├── app
├── include
│   └── head.h
├── lib
│   └── libcalc.so
├── main.c
└── src
    ├── add.c
    ├── div.c
    ├── mult.c
    └── sub.c

3 directories, 8 files

编译main.c

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# gcc main.c -o main
main.c:2:10: fatal error: head.h: No such file or directory
 #include "head.h"
          ^~~~~~~~
compilation terminated.

报错,和静态库使用的时候的原因一样

需要指定头文件位置、库文件位置、库文件名字

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# gcc main.c -o main -I include/
/tmp/ccOgCtBV.o: In function `main':
main.c:(.text+0x3a): undefined reference to `add'
main.c:(.text+0x5c): undefined reference to `subtract'
main.c:(.text+0x7e): undefined reference to `multiply'
main.c:(.text+0xa0): undefined reference to `divide'
collect2: error: ld returned 1 exit status
root@ubuntu:/home/ag/cpp/Linux/lesson06/library# gcc main.c -o main -I include/ -L lib/ -l calc
root@ubuntu:/home/ag/cpp/Linux/lesson06/library# ls
include  lib  main  main.c  src

可见,成功编译了,但是运行编译后的文件又报错

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# ./main
./main: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
动态库加载失败原因

造成上述错误的原因与动态库加载原理有关

库文件工作原理

◼ 静态库:GCC 进行链接时,会把静态库中代码打包到可执行程序中

◼ 动态库:GCC 进行链接时,动态库的代码不会被打包到可执行程序中

程序启动之后,动态库会被动态加载到内存中,通过 ldd (list dynamic dependencies)命令检查动态库依赖关系

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# ldd main
        linux-vdso.so.1 (0x00007fff808bb000)
        libcalc.so => not found
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007efca544e000)
        /lib64/ld-linux-x86-64.so.2 (0x00007efca5a41000)

可以看到,我们创建的动态库文件没有被找到,而标准的c库文件是被识别到的

如何定位共享库文件?

当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系

统的动态载入器来获取该绝对路径。

对于elf格式的可执行程序,是由 ld-linux.so 来完成的,它先后搜索 elf 文件的

DT_RPATH段 ——> 环境变量LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib//usr/lib目录,找到库文件后将其载入内存。

解决动态库加载失败问题

临时添加环境变量
root@ubuntu:/home/ag# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ag/cpp/Linux/lesson06/library/lib
root@ubuntu:/home/ag# echo $LD_LIBRARY_PATH
:/home/ag/cpp/Linux/lesson06/library/lib

此时可以在当前终端看到,编译后的文件main已经与动态库链接

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# ls
include  lib  main  main.c  src
root@ubuntu:/home/ag/cpp/Linux/lesson06/library# ldd main
        linux-vdso.so.1 (0x00007ffd93bf7000)
        libcalc.so => /home/ag/cpp/Linux/lesson06/library/lib/libcalc.so (0x00007f32c4b61000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f32c4770000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f32c4f65000)

关闭终端再打开其他终端,仍然会报错

永久配置环境变量
用户级别

回到home目录下(或者在当前目录下操作~/.bashrc也行)

root@ubuntu:/home/ag/cpp/Linux/lesson06/library# cd
root@ubuntu:~# ls
snap
root@ubuntu:~# ll
total 68
drwx------  7 root root 4096 Feb  5 21:14 ./
drwxr-xr-x 24 root root 4096 Feb  2 16:50 ../
-rw-------  1 root root 8887 Feb  9 06:36 .bash_history
-rw-r--r--  1 root root 3106 Apr  9  2018 .bashrc
drwx------  4 root root 4096 Nov  9 04:38 .cache/
drwx------  3 root root 4096 Nov  7 17:50 .gnupg/
-rw-r--r--  1 root root  148 Aug 17  2015 .profile
drwx------  6 root root 4096 Nov  8 03:06 snap/
drwx------  2 root root 4096 Nov  7 18:41 .ssh/
-rw-------  1 root root 9616 Feb  5 21:14 .viminfo
drwxr-xr-x  5 root root 4096 Feb  9 19:51 .vscode-server/
-rw-r--r--  1 root root  183 Nov  7 18:45 .wget-hsts
-rw-------  1 root root   52 Nov  7 18:38 .Xauthority

找到隐藏文件 .bashrc ,编辑该文件

vim .bashrc

使用shift+g跳转到最后一行,按o往下插入一行

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ag/cpp/Linux/lesson06/library/lib

保存退出,使修改后的bashrc生效:

root@ubuntu:~# . .bashrc
root@ubuntu:~# 

注:

1、. .bashrc等于source .bashrc

2、在终端1中配置bashrc后,在另一终端2中也要重新source一次才可以正常链接

系统级别

这时需要配置系统的profile文件

sudo vim etc/profile

同样在最后一行加入export环境变量即可

然后source一下(不用加sudo)

root@ubuntu:/etc# source etc/profile

如果识别不到,就source一下

静态库与动态库对比

程序编译成可执行程序的过程

静态库处理过程

动态库处理过程

静态库的优缺点

优点:
◆静态库被打包到应用程序中加载速度快

◆发布程序无需提供静态库,移植方便

缺点:
◆消耗系统资源,浪费内存

◆更新、部署、发布麻烦

动态库的优缺点

优点:
◆可以实现进程间资源共享(共享库)

◆更新、部署、发布简单

◆可以控制何时加载动态库

缺点:
◆加载速度比静态库慢

◆发布程序时需要提供依赖的动态库

Makefile

什么是 Makefile

​ 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就像一个 Shell 脚本一样,也可以执行操作系统的命令。

​ Makefile 带来的好处就是“自动化编译” ,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如 Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make。

Makefile 文件命名和规则

文件命名

makefile 或者 Makefile

Makefile 规则

一个 Makefile 文件中可以有一个或者多个规则

目标 ...: 依赖 ...
	命令(Shell 命令)
	...

⚫ 目标:最终要生成的文件(伪目标除外)
⚫ 依赖:生成目标所需要的文件或是目标
⚫ 命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进

Makefile 中的其它规则一般都是为第一条规则服务的。

一个简单的Makefile例子

app: sub.c add.c mult.c div.c main.c
	gcc sub.c add.c mult.c div.c main.c -o app

工作原理

◼ 命令在执行之前,需要先检查规则中的依赖是否存在

  • 如果存在,执行命令
  • 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令

◼ 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间

  • 如果依赖的时间比目标的时间晚,需要重新生成目标
  • 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被
    执行

升级一下之前的简单例子,检测每个依赖是否存在并且重新生成被修改的目标

app: sub.c add.c mult.c div.c main.c
	gcc sub.c add.c mult.c div.c main.c -o app

sub.o : sub.c
	gcc -c sub.c -o sub.o
add.o : add.c
	gcc -c add.c -o add.o
add.o : div.c
	gcc -c div.c -o div.o
mult.o : mult.c
	gcc -c mult.c -o mult.o
main.o : main.c
	gcc -c main.c -o main.o

但是这么写非常不简洁,文件一多就很麻烦

还得改进

变量

自定义变量

变量名 = 变量值

例如,var = hello

预定义变量
AR : 归档维护程序的名称,默认值为 ar

CC : C 编译器的名称,默认值为 cc

CXX : C++ 编译器的名称,默认值为 g++

$@ : 目标的完整名称

$< : 第一个依赖文件的名称

$^ : 所有的依赖文件
获取变量的值

$ (变量名)

例如,$(var)

模式匹配

' % '是通配符,可以匹配一个字符串

例如:%.o可以匹配出文件结尾为 .o 的所有文件

所以我们可以进行如下改写

add.o : add.c
	gcc -c add.c -o add.o
#等于
%.o : %.c
	gcc -c add.c -o add.o

gcc -c add.c -o add.o也应该简化掉

于是第三版简单例子可以写为:

src=sub.o add.o mult.o div.o main.o
target=app

$(target): $(src)
	$(CC) $(src) -o $(target)

%.o : %.c
	$(CC) -c $< -o $@

解释一下:

%.o : %.c 匹配到了src中的每一个文件

$< 则按顺序往下执行指令

src获取的方式还可以再优化

函数

$(wildcard PATTERN...)

​ 功能:获取指定目录下指定类型的文件列表

​ 参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔

​ 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔

​ 示例:

$(wildcard *.c ./sub/*.c)

​ 返回值格式: a.c b.c c.c d.c e.c f.c

$(patsubst <pattern>,<replacement>,<text>)

​ 功能:查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式,如果匹配的话,则以替换。可以包括通配符%,表示任意长度的字串。如果中也包含%,那么,中的这个%将是中的那个%所代表的字串。(可以用\来转义,以\%来表示真实含义的%字符)

​ 返回:函数返回被替换过后的字符串

​ 示例:

$(patsubst %.c, %.o, x.c bar.c)

​ 返回值格式: x.o bar.o

于是第四版简单例子可以改成:

#定义变量
# sub.o add.o mult.o div.o main.o
src=$(wildcard ./*.c)
objs=$(patsubst %.c, %.o,$(src))
target=app
$(target): $(objs)
	$(CC) $(objs) -o $(target)

%.o:%.c
	$(CC) -c $< -o $@

.PHONY:clean #伪目标,不生成clean	
clean:
	rm $(objs) -f

GDB调试

什么是 GDB

◼ GDB 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境,GDB 是 Linux 和许多类 Unix 系统中的标准开发环境。

◼ 一般来说,GDB 主要帮助你完成下面四个方面的功能:

​ 1.启动程序,可以按照自定义的要求随心所欲的运行程序

​ 2.可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)

​ 3.当程序被停住时,可以检查此时程序中所发生的事

​ 4.可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG

准备工作

◼ 通常,在为调试而编译时,我们会()关掉编译器的优化选项(-O), 并打开调试选项(-g)。另外,-Wall在尽量不影响程序行为的情况下选项打开所有warning,也可以发现许多问题,避免一些不必要的 BUG。

gcc -g -Wall program.c -o program

-g 选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件。

GDB 命令 –启动、退出、查看代码

启动和退出

gdb 可执行程序
quit

给程序设置参数/获取设置参数

set args 10 20
show args

查看当前文件代码

list/l (从默认位置显示)
list/l 行号 (从指定的行显示)
list/l 函数名(从指定的函数显示)

查看非当前文件代码

list/l 文件名:行号
list/l 文件名:函数名

设置显示的行数

show list/listsize
set list/listsize 行数

GDB命令 –断点操作

设置断点

b/break 行号
b/break 函数名
b/break 文件名:行号
b/break 文件名:函数

查看断点

i/info b/break

删除断点

d/del/delete 断点编号

设置断点无效/生效

dis/disable 断点编号
ena/enable 断点编号

设置条件断点(一般用在循环的位置)

b/break 10 if i==5 //比如有个for循环,这里是循环到i=5才会停在断点上

GDB命令 –调试命令

运行GDB程序

start(程序停在第一行)
run(遇到断点才停)

继续运行,到下一个断点停

c/continue

向下执行一行代码(不会进入函数体)

n/next

变量操作

p/print 变量名(打印变量值)
ptype 变量名(打印变量类型)

向下单步调试(遇到函数进入函数体)

s/step
finish(跳出函数体)

自动变量操作

display 变量名(自动打印指定变量的值)
i/info display
undisplay 编号

其它操作

set var 变量名=变量值 (循环中用的较多)
until (跳出循环)
posted @ 2023-02-24 15:58  dayceng  阅读(95)  评论(0编辑  收藏  举报