为什么一定要提供路径呢?
因为要根据路径找到文件
一切与路径相关的问题都是方便用户去访问文件
软硬链接
给我康康
软链接是这样的:
ln -s file_target1.txt file_soft.link
软链接有独特的innode
这是硬链接:
ln file_target2.txt file_hard.link
特征及使用场景
软链接是一个独立的文件,因为有独立的innode,软链接内容包含目标文件对应的路径字符串
硬链接是通过inode引用另外一个文件,软链接是通过名字引用另外一个文件
软链接像是Windows中的快捷方式(用绝对路径形成的,指向可执行程序)
软链接删掉不会影响目标文件(删快捷方式不影响应用本体)
软链接有何用呢?
就是快捷方式的作用
软链接为何要包含路径呢?路径唯一哎
硬链接不是一个独立的文件,硬链接的innode编号和文件一样,硬链接是一个文件名和innode的映射关系,建立硬链接就是在指定目录下,添加一个新的文件名和innode number的映射关系,指针指向同一个文件属性,指针计数为2
硬链接相当于重命名,属性中有一列硬链接数,文件的磁盘及引用计数:有多少文件名字符串通过innode指向文件,当引用计数为0时文件才相当于删除文件
定位一个文件只有两种方式:
1.通过路径
2.直接找到目标文件的innode
想必你已看清这个号和目录的关系
任何一个目录在刚开始新建的时候,引用计数一定是2
目录A内部新建一个目录,会让A目录的引用计数自动+1
一个目录内部有几个目录:A引用计数-2
构建Linux的路径结构,让我们可以使用.和..来进行路径定位
Linux系统中,不允许给目录建立硬链接
文件名是固定的,所有的系统指令在设定的时候可以知道.和..的作用
.和..都删不掉(如果你在目录内部是删除不了目录的)
《乌申克的救赎》
一般用硬链接做文件备份
打开的文件:内核、内存
没有被打开的文件和磁盘、文件系统有关
文本写入是语言的概念(缓冲区),二进制写入
int a = 1234567; //二进制写入
1234567 -> "1234567" //文本写入
谁转呢?
以文本方式写入?
C语言提供的函数转啊
由库函数转
动态库和静态库
我们用过C、C++的标准库
strerror,strstr、STL...
这是C标准库:
#include<iostream>
#include<string>int main()
{std::string name = "hahaha";std::cout << name << std::endl;return 0;
}
这是C++标准库
Linux中.so是动态库,.a是静态库
动态库是系统公有的资源
绝大部分的指令都是用C写的
Windows动态库:.dll,.lib
游戏玩家有福啦
是什么
x.o和y.o和z.o。。。这种被称为可重定位目标文件,他们链接后就形成可执行程序
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中,程序运行的时候将不再需要静态库
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码,在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间
操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间
/add.h/
#ifndef __ADD_H__
#define __ADD_H__
int add(int a, int b);
#endif // __ADD_H__
/add.c/
#include "add.h"
int add(int a, int b)
{return a + b;
}/sub.h/
#ifndef __SUB_H__
#define __SUB_H__
int sub(int a, int b);
#endif // __SUB_H__
/add.c/
#include "add.h"
int sub(int a, int b)
{return a - b;
}///main.c
#include <stdio.h>
#include "add.h"
#include "sub.h"int main(void)
{int a = 10;int b = 20;printf("add(10, 20)=%d\n", a, b, add(a, b));a = 100;b = 20;printf("sub(%d,%d)=%d\n", a, b, sub(a, b));
}
一个小故事:
励志轩是一位学计算机的大学牲,
她有位舍友名为墨墨酱,她俩正在课设阶段,课设内容是完成库的编写
励志轩听完老师的要求直呼稳勒稳勒
墨墨酱找到励志轩,说她不会能不能帮帮她
但是励志轩想的是,帮肯定是要帮的,但是源文件肯定不能一毛一样
于是励志轩采取了一个措施:
gcc -c mystdio.c
最终把这个编译完后的mystdio.o给了墨墨酱
励志轩打的算盘是,反正老师验收的时候是直接让跑代码,这样太容易蒙混过关了
函数方法的声明都放到头文件中
于是墨墨酱只需要编一个test.c测试一下运行就好勒:
#include"mymath.h"
#include"mystdio.h"
#include<stdio.h>
#include<string.h>int main()
{int a = 10;int b = 20;printf("%d+%d=%d\n", a, b, myAdd(a, b));myFILE* fp = my_fopen("./myfile.txt", "w");if (fp == NULL){return 1;}const char* message = "要不要出去玩...\n";my_fwrite(fp, message, strlen(message));my_fclose(fp);return 0;
}
什么?你说接口怎么实现的?那不关我的事啊宝
我只需要
gcc test.o mymath.o mystdio.o -o myexe
然后运行就好勒
游戏界面请认真辨别:
言归正传:
从这个样子再变成这样:
头文件是一个手册,提供函数的声明,告诉用户怎么用
于是墨墨酱顺利蒙混过关咯
老师又发布了新指令,《关于我的老师让我大一就提供一百个源文件这件事》
于是励志轩还是老样子,把.o给了墨墨酱
但是墨墨酱拷丢了一两个,怎么编译都过不去就很难受
然后励志轩就干脆把那一百多个文件打包准备给墨墨酱传过去
紧接着就传来一声
“且慢!”
“我不会解压”
“啊?”
好吧。。。那只能献出这一招:
ar -rc libmyc.a *.o
这是把源文件全打包,直接用就好,不需要解包
只不过在编译的时候需要把它也带上ww:
gcc main.c libmyc.a
所谓的库文件就是把所有的.o打包
为什么
那为什么要有这个捏?
为了爱与和平
及提高开发效率
ar是gnu归档工具,rc表示replace and create(存在就替换,不存在就打包)
库的名字去掉点后缀只有myc
但是不仅墨墨酱想用啊,暖暖,燃燃子...励志轩的很多亲友都想用
于是励志轩拉了个群,把自己打包好的.tgz放到群文件里
谁想用谁用吧
要把库安装到系统中:把自己的头文件拷贝到系统头文件的搜索路径下
把源文件拷贝到系统源文件的搜索路径下
高攀上了:
但是安装后编译又会发现编不过去
就是因为,,,编译器默认只认识C、C++的库
第三方提供的库编译器是不ins的
那怎么介绍它们认识啊?
gcc test.c -llibmyc.a
还是编不过啊
那怎么办啊
我要的是真名,要这样:
gcc test.c -lmyc
但是还是不建议非官方的安装到库里(太矬了)
库的安装和卸载都是改变系统指定的库
如果不安装还想在当前目录下用该怎么做呢??
可以这样告诉gcc搜索头文件的额外路径:
gcc test.c -I ./mylib/include/
但是还有对应的源文件怎么办捏?
这样就是顺便找一下库:
gcc main.c -I ./mylib/include/ -L ./mylib/lib
我都指定路径了能不能放过我
你指定的目录很多库啊,链接哪个库?
gcc test.c -I ./mylib/include/ -L ./mylib/lib -lmyc
-I:指定自定义头文件路径(这是i大写)
-L:指定用户自定义库文件路径
-l:指定执行的第三方库(这是l)
头文件制定了哦,在源代码里面就指定了呀,如果想要编译的时候不指定头文件路径,那在源码里做改动也是可以的:
#include"mylib/include/mymath.h"
#include"mylib/include/mystdio.h"
#include<stdio.h>
#include<string.h>int main()
{int a = 10;int b = 20;printf("%d+%d=%d\n", a, b, myAdd(a, b));myFILE* fp = my_fopen("./myfile.txt", "w");if (fp == NULL){return 1;}const char* message = "要不要出去玩...\n";my_fwrite(fp, message, strlen(message));my_fclose(fp);return 0;
}
gcc在默认编译的时候是动态链接的
可是我不是已经指定了相应的库吗?为何ldd之后不会显示出我指定的库呢?
一点都不酷
静态链接需要加上选项-static
但是-static是强制的静态链接,就是全部都需要是静态链接
而不加是有动态库就用,没有动态库的话静态库也能用
会拷贝到可执行程序里
编译期间告诉了gcc和g++所以编过去了,但是没告诉操作系统所以找不到,我们需要给动态库建立软链接,这样就都能找到咯
有个环境变量:
echo $LD_LIBRARY_PATH
这样配置环境变量以后就再不会重新加载咯
也是为了碟醋包了盘饺子
动态库要在程序运行的时候找到动态库加载并运行
静态库在编译期间已经在库中的代码拷贝到可执行程序的内部了,所以加载和库无关咯
综上就是:
1.安装的系统
2.建立软链接
3.命令行导入环境变量
4.修改.bashrc配置文件,让环境变量永久生效
还有个方法:
ls /etc/ld.so.conf.d
可以把要用的库的链接放到这个配置文件下
需要创建一个文件,后缀不能变
touch happy.conf
动态库的绝对路径贴进去
就完事勒
这是第五种方法,任您选择~
-static的意义是强制进行静态链接,要求我们链接的任何库都必须提供对应的静态库版本
怎么办
怎么生成静态库捏?
ar -rc libmymath.a add.o sub.o
ar是gnu归档工具,rc表示(replace and create)
这是查看静态库的目录列表:
ar -tv libmymath.a
t:列出静态库中的文件 v:verbose 详细信息
这是指定:
gcc main.c -L. -lmymath
-L 指定库路径 -l 指定库名 测试目标文件生成后,静态库删掉,程序照样可以运行
库的搜索路径:
从左到右搜索-L指定的目录
由环境变量指定的目录 (LIBRARY_PATH)
由系统指定的目录:
/usr/lib
/usr/local/lib
怎样生成动态库捏?
这是gcc、g++,生成共享库格式:
gcc -shared *.o -o libmyc.so
所谓的库文件,本质就是把.o打包成-FPIC
产生位置无关码:
gcc -fPIC -c mystdio.c
如果我们使用别人的库
别人会给我们提供一批头文件+一批库文件(.so,.a)
使用动态库的编译选项:
l:链接动态库,只要库名即可(去掉lib以及版本号)
L:链接库所在的路径.
系统有很多的库,他们通常由一组相互关联的用来完成某项常见工作的函数构成(比如用来处理屏幕显示情况的函数:ncurses库):
yum install -y ncurses
nothing to do:
再装个开发环境:
yum install -y ncurses-devel
耶耶:
可以借助这个库写个窗口小程序:
#include <ncurses.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>#define DELAY 100000int main() {int x, y, maxX, maxY; //蛇头的位置和终端窗口的大小int direction = KEY_RIGHT; //方向int snakeLength = 5; //蛇的长度int snakeX[100], snakeY[100]; //蛇身的位置int foodX, foodY; //食物的位置int score = 0; //得分int gameOver = 0; //游戏结束标志// 初始化ncurses库initscr();noecho();curs_set(0);keypad(stdscr, TRUE);timeout(0);// 获取终端窗口的大小getmaxyx(stdscr, maxY, maxX);// 初始化蛇的初始位置和长度x = maxX / 2;y = maxY / 2;for (int i = 0; i < snakeLength; i++) {snakeX[i] = x - i;snakeY[i] = y;}// 生成食物的初始位置srand(time(NULL));foodX = rand() % maxX;foodY = rand() % maxY;// 游戏循环while (!gameOver) {clear();// 绘制蛇for (int i = 0; i < snakeLength; i++) {mvprintw(snakeY[i], snakeX[i], "O");}// 绘制食物mvprintw(foodY, foodX, "*");// 显示分数mvprintw(0, 0, "Score: %d", score);// 移动蛇的位置int nextX = snakeX[0];int nextY = snakeY[0];switch (direction) {case KEY_UP:nextY--;break;case KEY_DOWN:nextY++;break;case KEY_LEFT:nextX--;break;case KEY_RIGHT:nextX++;break;}// 检查是否吃到食物if (nextX == foodX && nextY == foodY) {score++;snakeLength++;foodX = rand() % maxX;foodY = rand() % maxY;} // 移动蛇的身体for (int i = snakeLength - 1; i > 0; i--) { //后一节移动到前一节的位置snakeX[i] = snakeX[i - 1];snakeY[i] = snakeY[i - 1];}// 更新蛇头位置snakeX[0] = nextX;snakeY[0] = nextY;// 检查游戏结束条件//检查是否越界if (nextX < 0 || nextX >= maxX || nextY < 0 || nextY >= maxY) {gameOver = 1;}//检查是否撞到自己的身体for (int i = 1; i < snakeLength; i++) {if (snakeX[i] == nextX && snakeY[i] == nextY) {gameOver = 1;}}// 刷新屏幕refresh();// 延迟一段时间usleep(DELAY);// 获取用户输入int key = getch();switch (key) {case KEY_UP:case KEY_DOWN:case KEY_LEFT:case KEY_RIGHT:direction = key;break;case 'q':gameOver = 1;break;}}// 清理并退出ncurses库endwin();printf("Game Over! Your score: %d\n", score);return 0;
}
一个之前就遇到的报错,虽然我可能还是不知道为什么?
可能加上gnu之后支持的语法变多了?
gcc -Wall calc.c -o calc -lm
-lm表示要链接libm.so或者libm.a库文件
可执行程序和地址空间
访问动静态库本质也是访问文件
动态库加载和静态库无关
动态库本质是文件,存在磁盘上
加载到内存后要通过页表映射到对应的mm_struct中的共享区
所以可以访问共享区的代码(使用动态库)
动态库不一定只用于一个进程
不需要再加载到系统当中
动态库在加载之后,要映射到当前进程的堆栈之间的共享区
我们的可执行程序,编译成功,没有加载运行,那二进制代码中有地址吗?
#include<stdio.h>int Sum(int top)
{int i = 1;int ret = 0;for (; i <= top; i++){ret += i;}return ret;
}int main()
{int top = 100;int result = Sum(top);printf("result:%d\n", result);return 0;
}
写一份代码就知道嘞
转成反汇编:
objdump -S code > code.s
我们形成的可执行程序里面有地址
代码在编译完之后都有对应的地址
所以直接看源代码不用加载运行,可以在大脑中运行程序
在Linux中形成的可执行程序是ELF格式的
二进制有对应的固定格式
elf为可执行程序的头部,涵盖可执行程序的属性
可执行车行徐编译后悔形成很多汇编语句,每条汇编语句会有对应的地址
如何对每条语句进行编址捏?
就是0000000.....FFFFFFFF(平坦模式)
我们认为这是虚拟地址
地址有对应的种类
相对编址=地址+偏移量
ELF+加载器就称为各个区域的起始和结束地址,main函数的入口地址
进程=内核数据结构+代码和数据
进程在创建时是先形成内核数据结构,再加载代码和数据
创建地址空间
mm_struct是结构体对象,有成员变量
在进行初始化的时候
初始值从哪里来捏?
从可执行程序来捏
虚拟地址空间是一套设计标准,OS,编译器,加载器都要支持
代码也是数据,加载到内存中要有自己的物理地址
故事从main函数入口地址开始
CPU内存在寄存器pc指针,pc指针保存的是正在执行的下一指令的地址(虚拟地址):pc指向哪里,CPU就去执行哪里的代码
虚拟地址在左侧,物理地址在右侧,构建相对的映射
永远不会凉的群,全是应声虫:
CPU开始运行咯
pc指针告诉CPU(指哪打哪)
call指令里面的也是虚拟地址
库的编址和可执行程序的方式也差不多
objdump -S libmyc.so > test.s
库被映射到了什么位置捏?
一点都不影响哎
库原则上加载到堆上的任意位置都能工作
与地址无关码
只要映射完,虚拟地址随便变
改的只是虚拟地址,拿到偏移量还是能访问,来回跳转实现函数调用
库函数调用也是在地址空间内来回跳转
在操作系统中同时存在非常多的库
OS可以让进程知道库有没被加载