linux系统编程入门
GCC (GNU Compiler Collection GNU编译套件)
可以使用命令行控制编译器在翻译代码时应该遵循哪个c标准
安装命令:
sudo apt install gcc g++(版本大于4.8.5)
查看版本: gcc/g++ -v/—version
GCC工作流程
预处理:头文件展开 删除注释 替换宏
静态库的制作
静态库的使用
命令1:gcc main.c -o app -I ./include -l calc -L./lib
(
gcc main.c -o app 将main.c编译成为可执行文件app
-I(大写) ./include 在./include目录下搜索头文件
-l(小写) calc -L./lib 使用位于lib文件夹下的静态库
)
命令1 执行结果如下:
动态库的制作和使用
开始在lesson5文件中创建动态库,lesson6 tree结构如下:
接下来进入calc目录执行命令1:
命令1 gcc -c -fpic add.c div.c mult.c sub.c
用于编译阶段,产生的代码没有绝对地址,全部用相对地址。执行命令1 之后lesson5中src文件内容如下:
add.c add.o div.c div.o head.h main.c mult.c mult.o sub.c sub.o
命令2: gcc -shared add.o sub.o mult.o div.o -o libcalc.so
执行命令生成动态库文件
进入library目标执行命令3: cp …/calc/libcalc.so ./lib/
可以看到libcalc.so文件被复制进了lib目录下
命令4: gcc main.c -o main -i include/ -L lib/ -l calc
-i(小ai)是指定头文件的搜索路径 -L是指定搜索动态库的路径 -l指定动态库名
执行命令4之后运行main程序 发现报错
命令5: ldd main
下面了解解决动态库加载失败的方法
动态库加载失败的原因
程序运行起来后就是一个进程,该进程有自己独立的内存空间,DT_RPATH段是该内存空间的地址,无法改变。
LD_LIBRARY_PATH是环境变量
解决动态库加载失败问题
1.在终端中配置环境(只在一个终端中生效)
**命令1:**env //查看环境变量
命令2: pwd
获取绝对路径
命令3: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ccb/Linux/lesson6/library/lib
修改环境变量的值 $获取原环境变量的值
注意:这里配置的环境变量是在终端中配置的,换一个终端就失效了
命令4: echo $LD_LIBRARY_PATH
查看修改后的环境变量
命令5:ldd main
可以看到 libcalc.so=>不再是not found了
运行程序,运行成功:
2. 配置用户级环境变量
首先进入home目录下,命令1:
命令2:vim .bashrc
shift+g(跳转到文件的最后一行) o(插入一行,进入了插入模式)
插入:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ccb/Linux/lesson6/library/lib
按下esc键,从插入模式退出到命令模式,在命令模式下输入:wq保存并退出
命令3:source .bashrc
source命令也称为“点命令”,也就是一个点符号(.),是bash的内部命令。source命令通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。
**命令4:**ldd main
执行命令查看动态依赖关系列表,可见libcalc.so的指向不为空
3.配置系统级别的环境变量
清除用户级别环境变量之后执行如下操作
命令1: sudo vim /etc/profile
shift+g(跳转到文件的最后一行) o(插入一行,进入了插入模式)
插入:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ccb/Linux/lesson6/library/lib
按下esc键,从插入模式退出到命令模式,在命令模式下输入:wq保存并退出
命令2:source .bashrc
source命令也称为“点命令”,也就是一个点符号(.),是bash的内部命令。source命令通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。
4.修改/etc/ld.so.cache文件列表
首先删除在/etc/profile中配置的环境变量LD_LIBRARY_PATH
**命令1:**vim /etc/ld.so.cache
全是乱码,无法修改
命令2:sudo vim /etc/ld.so.conf
在ld.so.conf中插入路径名即可
命令3:sudo ldconfig
更新配置文件
执行main程序成功
静态库和动态库的对比
静态库制作过程
-L指定静态库路径
-l指定静态库名称
动态库制作过程
-fpic是生成与位置无关的目标代码。静态库链接时会把静态库代码打包到可执行程序中,代码的位置就固定了,加载到内存后位置不会发生改变。动态库加载时,不知道什么时候加载也不知道加载到内存中的哪一块区域
静态库的优缺点
每次更新静态库文件都要对整个程序重新进行编译
动态库的优缺点
Makefile
首先解压redis-5.0.10.tar.gz文件
tar zxvf FileName.tar.gz
进入lesson7文件内,并查看文件内容:
- makefile中的其他规则一般都是为第一条规则服务的
makefile初体验:
进入lesson7的目录下,输入vim Makefile ,在Makefile文件中输入如下指令:
执行make指令,可以看到lesson7目录下多出了一个app文件,运行app文件即可
若生成目标之后依赖发生了变化,则目标会重新生成
若生成目标时没有依赖,则认为依赖的时间总是比目标时间早一些
第二版makefile文件
命令1:cp Makefile Makefile1
保存第一版Makefile文件到Makedile1中
命令2:vim Makefile
命令3:make
执行Makefile内容
相比与第一个版本,第二个版本在更新依赖时只需更新发生了更改的依赖,而第一个版本在更新依赖时要更新所有的依赖。
下述操作将验证上面的说法:
1.打开main.c文件进行修改(可以加个换行)
2.执行make指令,可以看到只更新了main.o依赖并重新生成了app文件
从自定义变量、模式匹配和函数这三个写法修改Makefile文件
Makefile中的变量
自定义变量
变量名=变量值 var=hello
◼ 预定义变量
AR : 归档维护程序的名称,默认值为 ar
CC : C 编译器的名称,默认值为 cc
CXX : C++ 编译器的名称,默认值为 g++
$@ : 目标的完整名称
$< : 第一个依赖文件的名称
$^ : 所有的依赖文件
◼ 获取变量的值
$(变量名) $(var)
模式匹配
%.o:%.c
- %: 通配符,匹配一个字符串
- 两个%匹配的是同一个字符串
%.o:%.c
gcc -c $< -o $@
第三版Makefile
命令1:cp Makefile Makefile2
将第二版的Makefile文件保存 到Makefile2中
命令2:vim Makefile
在Makefile中输入下图所示命令:
命令3:rm *.o app
删除.o文件和app
命令4:make
执行make指令,结果如下图所示:
将Makefile文件修改成如下内容:
将lesson7目录库下的.o文件和app程序删除,执行make指令重新生成app,生成之后运行app,结果如下:
函数
$(wildcard PATTERN…)
功能:获取指定目录下指定类型的文件列表
参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多 个目录,一般使用空格间隔
返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
示例: $(wildcard *.c ./sub/ *.c) 返回值格式: a.c b.c c.c d.c e.c f.c
$(patsubst ,,
功能:查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合 模式,如果匹配的话,则以替换。
可以包括通配符%
,表示任意长度的字串。如果 中也包含%
,那么,中的这个%
将是中的那个% 所代表的字串。(可以用\
来转义,以\%
来表示真实含义的%
字符)
返回:函数返回被替换过后的字符串
示例: $(patsubst %.c, %.o, x.c bar.c) 返回值格式: x.o bar.o
使用wildcard和patsubst函数对Makefile文件修改,修改后的内容如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9hd4yXtH-1667723654063)(D:\typora笔记\linux服务器开发\pics\image-20221031100952028.png)]
在Makefile中添加clean规则,用于清除依赖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0f6TLauk-1667723654063)(D:\typora笔记\linux服务器开发\pics\image-20221031101904140.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z88wK2Wz-1667723654063)(D:\typora笔记\linux服务器开发\pics\image-20221031101958726.png)]
新建一个clean文件,之后执行clean操作,clean操作并没有执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-esj7jQdw-1667723654063)(D:\typora笔记\linux服务器开发\pics\image-20221031104014040.png)]
生成clean时没有依赖,认为依赖的时间总是比clean时间早一些,因此不会执行clean操作了
可以将clean作为一个伪目标,这样不会生成一个目标文件,不会比较目标和依赖的时间先后关系,也就可以执行clean操作了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGqmy5bV-1667723654064)(D:\typora笔记\linux服务器开发\pics\image-20221031103851145.png)]
GDB
GDB是由GNU软件系统社区提供的调试工具,同GCC配套组成了一套完整的开发环境,GDB是Linux和许多类Unix系统中的标准开发环境。
一般来说,GDB 主要帮助你完成下面四个方面的功能:
- 启动程序,可以按照自定义的要求随心所欲的运行程序
- 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
- 当程序被停住时,可以检查此时程序中所发生的事
- 可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG
准备工作:
通常,在为调试而编译时,我们会()关掉编译器的优化选项(-O
), 并打开调 试选项(-g
)。另外,-Wall
在尽量不影响程序行为的情况下选项打开所有 warning,也可以发现许多问题,避免一些不必要的 BUG。
gcc -g -Wall program.c -o program
-g
选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机 器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件
为验证以上理论,先加入-g指令生成含有源代码的可执行文件test,再生成不含有源代码信息的可执行程序test1,比较两个程序的大小,发现test确实比test1大一些,test文件比test1文件多了源代码信息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OiVl5dbe-1667723654064)(D:\typora笔记\linux服务器开发\pics\image-20221031144353323.png)]
gdb命令:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vq08W7vP-1667723654064)(D:\typora笔记\linux服务器开发\pics\image-20221031110956390.png)]
输入gdb test命令进入gdb模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PEAzHKgV-1667723654064)(D:\typora笔记\linux服务器开发\pics\image-20221031144830619.png)]
在gdb中给程序设置参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PgWmpjvp-1667723654065)(D:\typora笔记\linux服务器开发\pics\image-20221031145144054.png)]
可以使用vim查看代码(使用vim查看代码时 通过命令 :set nu可以显示代码行号),也可以使用gdb命令查看代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gI11QGla-1667723654065)(D:\typora笔记\linux服务器开发\pics\image-20221031145947920.png)]
使用gdb调试时(使用ctrl+l清空命令窗口),源代码文件(不能改名字)和可执行程序要在一起
输入一个list显示10行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AJBHYjsf-1667723654065)(D:\typora笔记\linux服务器开发\pics\image-20221031151518509.png)]
给list加入参数 比如函数名或者代码行数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ez1plsVX-1667723654065)(D:\typora笔记\linux服务器开发\pics\image-20221031151731130.png)]
使用gcc编译.cpp文件会报错
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y1kSDYvL-1667723654066)(D:\typora笔记\linux服务器开发\pics\image-20221031152110077.png)]
应使用g++编译.cpp文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NSI4fuGF-1667723654066)(D:\typora笔记\linux服务器开发\pics\image-20221031152250336.png)]
使用gdb打开main
查看非当前文件代码,如查看bubble.cpp中的代码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UNqYjgsR-1667723654066)(D:\typora笔记\linux服务器开发\pics\image-20221031152731015.png)]
修改list指令显示的代码行数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8cjBdiIS-1667723654066)(D:\typora笔记\linux服务器开发\pics\image-20221031153116503.png)]
断点操作:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hU5h8pdt-1667723654067)(D:\typora笔记\linux服务器开发\pics\image-20221031111017143.png)]
添加断点:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WBNxCKWu-1667723654067)(D:\typora笔记\linux服务器开发\pics\image-20221031162117061.png)]
删除断点:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BQ7S1GuB-1667723654067)(D:\typora笔记\linux服务器开发\pics\image-20221031162353443.png)]
设置断点无效:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mR8bCkk1-1667723654067)(D:\typora笔记\linux服务器开发\pics\image-20221031162712456.png)]
设置条件断点:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZsNzaOZH-1667723654068)(D:\typora笔记\linux服务器开发\pics\image-20221031162932061.png)]
调试命令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bt9TNoZx-1667723654068)(D:\typora笔记\linux服务器开发\pics\image-20221031111038421.png)]
进入Linux lesson8目录下,输入start指令,程序停在int main()这一行
输入c之后运行全部程序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lDTjXjPJ-1667723654068)(D:\typora笔记\linux服务器开发\pics\image-20221101123901432.png)]
退出调试main程序,调试test,并给添加三个断点:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KC7BN4ft-1667723654068)(D:\typora笔记\linux服务器开发\pics\image-20221101130655287.png)]
输入run指令,程序开始运行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fhvDOWQg-1667723654069)(D:\typora笔记\linux服务器开发\pics\image-20221101130902433.png)]
我们想要查看变量a和变量b的值,可以使用print指令打印a和b的值,但是每次查看都要输入两次print指令,有些麻烦。可以使用自动变量操作,每次运行之后自动打印a和b的值:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eAqcJXg0-1667723654069)(D:\typora笔记\linux服务器开发\pics\image-20221101131154013.png)]
查看已存在的display情况(info display)&删除自动变量(undisplay number):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0KAmJifF-1667723654069)(D:\typora笔记\linux服务器开发\pics\image-20221101131550648.png)]
文件IO
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7EyoHyF4-1667723654069)(D:\typora笔记\linux服务器开发\pics\image-20221101190037589.png)]
fopen打开一个文件之后会返回一个FILE类型的指针,该指针指向一个结构体。结构体中含有文件描述符/文件句柄(整型值)、文件读写指针位置和I/O缓冲区(内存地址)。
什么情况下数据会从内存刷新到磁盘:
- 缓冲区满了才会讲缓冲区内容写入磁盘(会导致文件写入的实时性不高)
- 调用fflush指令刷新缓冲区
- 正常关闭文件
标准c库fopen指令比linux打开文件的指令效率更高,因为有缓存区的存在。但正是缓冲区的存在导致了文件写入实时性不高,因此在网络编程中一般使用linux的写入指令。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rXrKm038-1667723654070)(D:\typora笔记\linux服务器开发\pics\image-20221101192114842.png)]
虚拟地址空间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5eoE3CTA-1667723654070)(C:\Users\17413\AppData\Roaming\Typora\typora-user-images\image-202211021012176946.png)]
堆空间是从低地址往高地址存,栈空间是从高地址往低地址存。
主存和内存的定义
需要明确主存是指存放当前正要执行或者刚执行完毕的程序和数据的存储器是主存储器。
存放正在执行的程序或正在使用的数据是cache,cache解决了CPU与主存速度匹配的问题。
一般将主存和cache统称为内存。
虚拟地址空间解决的是主存容量和价格的矛盾,使速度接近主存而价格接近外存。
编译器中的变量地址是逻辑地址,MMU会将逻辑地址映射为物理地址(主存储器上是实际地址)
文件描述符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLJBscfe-1667723654070)(D:\typora笔记\linux服务器开发\pics\image-20221102113330837.png)]
文件描述符表是一个数组,大小默认是1024,前三个被占用(标准输入 标准输出 标准错误)。
Linux系统IO函数
- int open(const char *pathname, int flags);
- int open(const char *pathname, int flags, mode_t mode);
- int close(int fd);
- ssize_t read(int fd, void *buf, size_t count);
- ssize_t write(int fd, const void *buf, size_t count);
- off_t lseek(int fd, off_t offset, int whence);
- int stat(const char *pathname, struct stat *statbuf);
- int lstat(const char *pathname, struct stat *statbuf);
在lesson9目录下,open.c使用函数1,create.c使用函数2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q3M9Yq9e-1667723654070)(D:\typora笔记\linux服务器开发\pics\image-20221104154557086.png)]
int open(const char *pathname, int flags, mode_t mode);//可变参数
参数:
flags:对文件操作权限的设置和其他设置。flags参数是一个int类型的数据,占4个字节,32位
flags 32个位 每一位都是一个标志位
- 必选项:O_RDONLY, O_WRONLY, or O_RDWR. 三个操作互斥
These request opening the file read-only, write-only,or read/write
- 可选项:O_CREAT 文件不存在,创建新文件
mode:八进制的数,表示用户对新创建文件的操作权限,比如:0775
最终的权限是:mode & ~umaskumask的作用是抹去某些权限
返回值:
open(), openat(), and creat() return the new file descriptor,or -1 if an error occurred
create.c中的代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdio.h>
#include<unistd.h>
int main(){
//创建一个新的文件
int fd= open("create.txt",O_RDWR | O_CREAT,0777);
if(fd==-1){
perror("open");
}
//关闭
close(fd);
return 0;
}
需要注意的是函数2中的mode是一个八进制数,该数表示用户对新创建文件的操作权限,比如:0775,但最终的权限是由mode & ~umask决定的,umask的作用是抹去某些权限。在create.c的代码中,我们设置了mode是0777,但是对create.c编译运行之后产生的create.txt文件的操作权限如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QfAELVyG-1667723654071)(D:\typora笔记\linux服务器开发\pics\image-20221104160602519.png)]
返回值:
open(), openat(), and creat() return the new file descriptor,or -1 if an error occurred
create.c中的代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdio.h>
#include<unistd.h>
int main(){
//创建一个新的文件
int fd= open("create.txt",O_RDWR | O_CREAT,0777);
if(fd==-1){
perror("open");
}
//关闭
close(fd);
return 0;
}
需要注意的是函数2中的mode是一个八进制数,该数表示用户对新创建文件的操作权限,比如:0775,但最终的权限是由mode & ~umask决定的,umask的作用是抹去某些权限。在create.c的代码中,我们设置了mode是0777,但是对create.c编译运行之后产生的create.txt文件的操作权限如下:
[外链图片转存中…(img-QfAELVyG-1667723654071)]
操作权限为:rwx rwx r-x(111 111 101,即775),我们设置的mode为0777,但是实际文件的操作权限为0775,可见实际操作权限并不仅仅由我们设置的mode决定。