提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:开发工具(Ubuntu+Xshell+VS Code)
一 常用的Linux系统命令符
ls ——查看当前目录下的文件
pwd ——看当前所在文件夹
cp calc library ../lession06 -r
//-r ,目录文件,递归调用
//cp,copy文件
rm+文件名.后缀/*.后缀 //删除当前目录下的文件
touch name.txt
mkdir name //创建文件夹
ln -s a.txt b.txt //创建软链接
ls -l //当前目录所有文件信息
env //查看系统变量
二 GCC
gcc *.c -o app(可执行文件名)
三 静态库
四 动态库(共享库)
动态库
◼ 命名规则:
◆ Linux : **lib*xxx*.so**
lib : 前缀(固定)
xxx : 库的名字,自己起(使用库时的用名)
.so : 后缀(固定)
在Linux下是一个可执行文件
◆ Windows : **lib*xxx*.dll**
◼ 动态库的制作:
◆ gcc 得到 .o 文件,得到和位置无关的代码(–fpic)
gcc -c –fpic/-fPIC a.c b.c
◆ gcc 得到动态库
gcc -shared a.o b.o -o libcalc.so
◼ 动态库加载失败的原因:
◆ 动态库:gcc进行链接时,动态库的代码不会被打包到可执行文件中,(只包含动态库的一些信息)
◆ 编译时不报错是因为只是把动态库的信息放到可执行程序中,没有加载动态库的内容到内存中。
◆运行时找不到动态库——没有指明动态库的绝对路径
◆如何定位共享库文件呢?
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。
此时就需要系统的动态载入器来获取该绝对路径。
对于elf格式的可执行程序,是由ld-linux.so来完成的,
它先后搜索elf文件的 DT_RPATH段 (无法改变)
——> 环境变量LD_LIBRARY_PATH
——> /etc/ld.so.cache文件列表
——> /lib/,/usr/lib目录找到库文件后将其载入内存。
◼ 动态库加载失败的解决方法(配置环境变量):
方法一:环境变量LD_LIBRARY_PATH
◆终端下配置环境变量(临时性)
echo $LD_LIBRARY_PATH:/home/coder/Linux/lession06/library/lib
·◆用户级别的配置环境变量(永久性)
vim .bashrc (根目录下打开并添加以下环境变量)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:
/home/coder/Linux/lession06/library/lib
. .bashrc 或 source .bashrc (使得环境变量生效)
◆系统级别的配置环境变量(永久性)
sudo vim /etc/profile
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:
/home/coder/Linux/lession06/library/lib
source /etc/profile //刷新
方法二:/etc/ld.so.cache文件列表
sudo vim /etc/ld.so.conf
/home/coder/Linux/lession06/library/lib
sudo ldconfig
五 动态库和静态库的区别
加载速度快慢,可扩展性,资源利用率
六 Makefile
好处:自动化编译
普通程序员只需看得懂别人写的,架构师要会写
◼ 文件命名
makefile 或者 Makefile
◼ Makefile 规则
一个 Makefile 文件中可以有一个或者多个规则
目标 ...: 依赖 ...
命令(Shell 命令)
...
目标:最终要生成的文件(伪目标除外)
依赖:生成目标所需要的文件或是目标
命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进)
Makefile 中的其它规则一般都是为第一条规则服务的。
◼ 命令在执行之前,需要先检查规则中的依赖是否存在
如果存在,执行命令
如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,
如果找到了,则执行该规则中的命令
◼ 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
如果依赖的时间比目标的时间晚,需要重新生成目标
如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
变量
◼ 自定义变量
变量名=变量值 var=hello $(var)
◼ 预定义变量
AR : 归档维护程序的名称,默认值为 ar
CC : C 编译器的名称,默认值为 cc
CXX : C++ 编译器的名称,默认值为 g++
$@ : 目标的完整名称
$< : 第一个依赖文件的名称
$^ : 所有的依赖文件
◼ 获取变量的值
$(变量名)
app:main.c a.c b.c
gcc -c main.c a.c b.c
#自动变量只能在规则的命令中使用
app:main.c a.c b.c
$(CC) -c $^ -o $@
模式匹配
%.o:%.c
- %: 通配符,匹配一个字符串
- 两个%匹配的是同一个字符串
%.o:%.c
gcc -c $< -o $@
Makefile 的函数
◼ $(wildcard PATTERN...)
功能:获取指定目录下指定类型的文件列表
参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多
个目录,一般使用空格间隔
返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
示例:
$(wildcard *.c ./sub/*.c)
返回值格式: a.c b.c c.c d.c e.c f.c
◼ $(patsubst <pattern>,<replacement>,<text>)
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合
模式<pattern>,如果匹配的话,则以<replacement>替换。
<pattern>可以包括通配符`%`,表示任意长度的字串。如果<replacement>
中也包含`%`,那么,<replacement>中的这个`%`将是<pattern>中的那个%
所代表的字串。(可以用`\`来转义,以`\%`来表示真实含义的`%`字符)
返回:函数返回被替换过后的字符串
示例:
$(patsubst %.c, %.o, x.c bar.c)
返回值格式: x.o bar.o
定义伪目标:.PHONY:<目标名> (在Makefile 文件添加)
七 GDB调试
gdb -v //查看gdb版本
编译可执行文件时,加入-g,带入源代码的信息
gcc hello.c -o hello -g //编译
gdb hello //打开
l //查看代码
b 10 //在代码的第十行添加断点
i b //查看断点的信息
r //运行
n //next. 往下执行
c //continue ,继续运行
一般用g++编译.cpp文件,用gcc编译的话,可能报错(找不到C++的库)
设置断点
调试命令
跳出循环——循环体中的断点设为不可用
八 文件IO
实现跨平台的两种方式
1. Java针对不同的平台开发Java虚拟机
2.标准C库,在不同平台调用系统API
标准C库的IO函数效率高于Linux系统IO,因为前者带有缓冲区
九 虚拟地址空间
产生背景
1.实际运行的应用程序需要耗费的内存总数 > 计算机内存
2.调度产生的内存碎片造成内存地址空间断裂
程序:磁盘上的代码
进程:磁盘上的代码加载到内存上运行
.
32位的计算机的虚拟地址空间:2的32次方 = 4G个地址
64位:2的48次方
以32位计算机为例
1.高地址的1G为内核区;剩余为用户区
内核区 | 用户区 |
---|---|
普通用户不能操作 | 栈(小,高到低): |
可以通过系统调用操作 | 堆(大,低到高): |
right-aligned 文本居右 |
2.虚拟地址空间里的数据会被CPU中的内存管理单元(MMU)映射到真实的物理内存上
文件描述符
存在内核区的PCB(进程控制块)中的文件描述符表(1024的数组“前3个默认”)
同一个文件可以被打开多次,但是文件描述符不一样
man 2 函数名: Linux系统函数API
man 3 函数名:标准C库函数API
open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//打开一个已经存在的文件
int open(const char *pathname, int flags);
-pathname:要打开的文件路径
-flags:对文件的操作权限设置,还有其他设置
O_RDONLY, O_WRONLY, or O_RDWR
-返回值:返回一个新的文件描述符,如果调用失败,返回-1.
errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号
#include <stdio.h>
void perror(const char *s);
作用:打印errno对应的错误描述
-s:用户描述,比如hello,最终输出: hello:xxx(实际的错误)
//创建一个新文件
int open(const char *pathname, int flags, mode_t mode);
create
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
-pathname:要打开的文件路径
-flags:对文件的操作权限设置,还有其他设置
必选项:O_RDONLY, O_WRONLY, or O_RDWR (两两互斥)
可选项:O_CREAT文件不存在则创建
flags是一个int类型的数据,4B
32位,每一位代表一种标志位,所以采用按位或可以叠加“可选项”和“必选项”。
-mode:八进制数,表示用户对新创建文件的操作权限,例如:0775
最终的权限:mode & ~umask
umask的作用:抹去某些权限
copy
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd:文件描述符,open得到的,通过fd操作某个文件
buf:需要读取数据存放的地方,数组的地址(传出参数)
count:指定数组的大小
返回值
成功:
>0:返回实际读取到的字节数
=0:文件已经读取完毕
失败:-1,并设置errno
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd:~
buf:往磁盘写入的数据
count:写入数据的实际大小
返回值
成功:实际写入的字节数
失败:-1,并设置errno
lseek
/*
//标准C库
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
//Linux系统
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
fd:文件描述符,open得到,通过fd操作文件
offset:偏移量
whence:
SEEK_SET, 设置文件指针的偏移量
SEEK_END, 设置偏移量:当前位置+第二个参数offset的值
SEEK_CUR, 设置偏移量,文件大小+第二个参数offset的值
函数作用:
1.移动文件指针到头文件:lseek(fd,0,SEEK_SET);
2.获取当前文件指针的位置:lseek(fd,0,SEEK_CUR);
3.获取文件长度:lseek(fd,0,SEEK_END);
4.拓展文件长度,增加100字节:lseek(fd,100,SEEK_END);
之后还要写入一次数据拓展才能有效
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>