gcc编译过程
需要安装gcc:sudo apt-get install gcc
gcc -E hello.c -o hello.i:预处理hello.c文件,并且指定预处理后的文件名字为hello.i
gcc -S hello.i -o hello.s:编译hello.i文件,生成汇编文件
gcc -c hello.s -o hello.o:汇编生成二进制文件
gcc hello.o -o app:链接生成可执行文件
gcc hello.c -o app:直接生成可执行文件,其他步骤默认处理并且不保留中间文件,不指定名称时生成 a.out.
gcc hello.c -I ./include -o app:编译时,指定头文件的目录(参数为大写的 i )
gcc hello.c -o app -I ./include -D DEBUG:编译时,指定宏(相当于程序内增加#define DEBUG)
-O3:编译时,优化代码,最高等级优化为3
-Wall:编译时,输出警告信息
-g:增加调试信息
静态库
- 命名规则:lib + 库的名字 + .a
- 制作过程:
- 生成对应的 .o文件:gcc -c命令 ;如:gcc *.c -c -I…/include (将当前目录中的所有.c文件进行汇编,默认生成名字,头文件在上一级目录的include目录内)
- 将生成的 .o文件打包:ar rcs 生成的静态库的名字 生成的所有的.o ;如:ar rcs libMyCalc.a *.o - 发布和使用静态库
- 发布静态库
- 头文件
一般文件结构可以列为:
4. 使用
在main.c中引用头文件并使用其中的函数,用户只需要使用头文件和静态库.a文件;
编译:gcc main.c …/lib/libMyCalc.a -o sum -I…/include
指定静态库及头文件位置
或者:gcc main.c -I…/include -L lib -l MyCalc -o sum (-L指定静态库的目录,-l指定静态库名称,注意前者为大写i,后面的为小写L)
目录结构:
.c文件
头文件
#ifndef __HEAD_H_
#define __HEAD_H_
int add(int a,int b);
int div(int a,int b);
#endif
main.c
#include<stdio.h>
#include<head.h>
int main(void)
{
int sum = add(3,4);
printf("sum = %d",sum);
return 0;
}
nm libMyCalc.a:查看静态库的组成,也可以查看可执行文件
优缺点
使用静态库打包,编译的最小单位是.o,不会打包.a,使用到了.o中的哪个函数就打包哪个.o 。
优点:
1. 发布程序的时候,不需要提供对应的库;
2. 库的加载速度快
缺点:
1. 库被打包到应用程序中,导致应用程序很大
2. 库发生了改变,应用程序需要重新编译
动态库(共享库)
对应于windows中的dll.
linux每个运行的程序(进程)操作系统都会为其分配一个0-4G的地址空间(虚拟地址空间),虚拟内存占用硬盘空间:其分配如下:
-
命名规则: lib + 库名字 + .so
-
制作步骤:
-
生成与位置无关的代码(.o)
gcc -fPIC -c *.c -I../include
-
将.o打包为共享库(动态库)
gcc -shared -o libMyCalc.so *.o -I../include
-
-
发布和使用共享库
-
制作完成后,可以将 libMyCalc.so 和 head.h 发布出去
-
使用:
gcc main.c lib/libMyCalc.so -o app -I../include main.c中使用了头文件中的函数,或者: gcc main.c -I../include -L../lib -lMyCalc -o myapp
使用第二种方法时,运行myapp报错
提示:./myapp: error while loading shared libraries: libMyCalc.so: cannot open shared object file: No such file or directoryldd myapp 查看可执行程序(myapp)在执行的时候依赖的所有的动态库
-
-
解决程序执行时动态库无法被加载的问题
程序执行的时候会根据环境变量去查找动态库,其过程如下:
由于当前动态库不在系统的/lib目录中,为了找到动态库,需要配置环境变量:LD_LIBRARY_PATH,之后调用会先查找此目录中的动态库。export LD_LIBRARY_PATH=../lib 临时测试动态库的环境变量设置,关闭终端后无效其中路径为动态库所在相对路径,相对当前输入命令时所处的目录
-
配置文件.bashrc 最后一行增加绝对目录(动态库所在路径)
-
找到动态连接器的配置文件(/etc/ld.so.conf),将动态库的路径写到里面,再更新配置
sudo ldconfig -v
-
-
优缺点
使用动态库时,打包应用程序时,并没有将库打包到程序中,只是做了一个标记,运行后,相关的库才加载到内存中。
**优点:**执行程序包体积小;动态库更新后(接口不变),不需要重新编译应用程序(如上main.c),需要把动态库提供给用户。
**缺点:**动态库没有打包到应用程序中,加载起来相对较慢。
Windows的库
**dll文件:**它是应用程序调用dll运行时,真正的可执行文件。dll应用在编译、链接成功后,.dll文件即存在。开发成功后的应用程序在发布时,只需要有.exe文件和.dll文件,不必有.lib文件和dll头文件。
dll的引入库文件:(即动态库的lib) 它是dll在编译、链接成功后生成的文件。主要作用是当其它应用程序调用dll时,需要将该文件引入应用程序。否则,dll无法引入。
- lib是编译时需要的,dll是运行时需要的(动态库文件,vs动态库包含lib和dll文件)。
如果要完成源代码的编译,有lib就够了。
如果也使动态连接的程序运行起来,有dll就够了。
在开发和调试阶段,当然最好都有。 - 一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。
- 在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。
动态链接库 (DLL) 是作为共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。
动态链接与静态链接的不同之处在于:动态链接允许可执行模块(.dll 文件或 .exe 文件)仅包含在运行时定位 DLL 函数的可执行代码所需的信息。在静态链接中,链接器从静态链接库获取所有被引用的函数,并将库同代码一起放到可执行文件中。
使用动态链接代替静态链接有若干优点。DLL 节省内存,减少交换操作,节省磁盘空间,更易于升级,提供售后支持,提供扩展 MFC 库类的机制,支持多语言程序,并使国际版本的创建轻松完成。
GCC
gcc工作流程
-
预处理 gcc -E
-
编译 gcc -S 最耗时
-
汇编 gcc -c
-
链接 没有参数
-o:指定生成文件的名字
-D:在编译的时候定义宏(一般用于控制log的输出)
-I:指定头文件的位置
-g:gdb调试的时候需要该参数
-O:编译优化,3个等级-O1,-O2,-O3
-Wall:编译期间输出警告信息
制作静态库
1.命名规则:libMyName.a
2.步骤:
1.生成 .o 文件 : gcc -c *.c(将所有的.c文件进行处理), 汇编文件生成二进制文件
2.打包(将所有的.o文件打包):ar rcs 静态库的名字 *.o
3.使用:main.c
gcc main.c (库) -LlibPath -llibname -o app -Iinclude
(要编译的文件,这里是main.c;库路径-L;库名字-l(小些L);指定生成文件的名字-o;头文件的路径-I(大写i))
制作动态库
- 动态库后缀:.so 命名:libMyName.so
- 步骤:
- 生成与位置无关的代码.o gcc -fPIC -c *.c -Iinclude
- 将.o打包生成.so文件 gcc -shared -o libMyTest.so *.o
- 使用:main.c(引用的文件) lib(库目录) include(头文件目录)
gcc main.c -Llib -lMyTest -Iinclude -o app - 应用程序不能执行,动态链接器链接不到自己制作的库
- 临时设置方法:
linux中有一个环境变量,将连接器目录导入该变量export LD_LIABRARY_PATH = ./lib;终端关闭后失效 - 永久设置方法:
1.找到动态链接库的配置文件:/etc/ld.so.conf
2.在该文件中添加动态库的目录(绝对路径)
3.更新:sudo ldconfig -v(-v查看配置信息)
- 临时设置方法:
gbd调试
先生成可执行文件: gcc *.c -o app -g
使用-g带调试信息,生成app
可执行文件,执行文件后可以看到打印信息。可以通过ls -l查看带有调试信息的可执行文件的大小要比普通文件大。
- 启动gdb,进入gdb模式:
gdb app
- 在gdb模式查看源代码:
l
- 查看指定文件(:行数):
l file.c:20
- 查看指定文件里的函数:
l file.c:selectionSort
继续输入命令l
,列出该函数之后的内容,之后可以输入回车,重复上一步的操作。 - 指定行打断点:
break 22
或者b 22
(b file.c:20
) - 条件断点:
b 12 if i==15
只有当i等于15时,12行的断点才触发(只能停在for循环内部的行数) - 查看断点:
info break
或者i b
- 开始调试:
start
(只执行一步)或者run
- 单步调试:
n
(next) - 执行到下一断点:
c
(continue) - 进入函数体:
s
(step) - 查看函数体内的源代码:
l
(list) - 查看断点停留处变量(j)的值:
p j
(print) - 查看变量类型:
ptype j
- 跟踪变量的值:
display i
跟踪变量i的值,接下来每次调试下一步都会展示变量 i 的值 - 取消跟踪变量:
undisplay 1(变量编号,通过info display查看)
- 跳出循环:
u
- 从进入到的函数体内部回到进入位置:
finish
- 删除断点:
delete 断点对应编号(Num)
或者缩写d 断点对应编号(Num)
- 设置变量值(循环中跳到变量值为某个数的循环中):
set var i=10
- 退出gdb:
quit
makefile
当项目文件较多,目录很大时,gcc命令就会显复杂和难以使用。
-
makefile文件的名字一般全部使用小写字母或者只有首字母大写。
-
规则
三要素:目标(生成app的名字),依赖(生成app的条件,比如.c文件),命令
目标:依赖条件
(tab缩进) 命令
比如:app:main.c add.c sub.c mul.c gcc main.c add.c sub.c mul.c -o app
能够只执行对应文件修改的.o文件(从上到下执行,直到最上面的目标可以生成)
通过比对文件时间,如果生成的文件不是最新的,则编译对应的文件(当然如果变动了,最后的app肯定会执行)
app:main.o add.o sub.o mul.o gcc main.o add.o sub.o mul.o -o app main.o:main.c gcc -c main.c add.o:add.c gcc -c add.c sub.o:sub.c gcc -c sub.c mul.o:mul.c gcc -c mul.c
修改后:
使用makefile中自定义变量的值时,需要在前面加$()
% 在查找最终目标生成依赖的.o文件时,匹配对应的项;
$<:makefile中的自动变量,规则中的第一依赖
$@: makefile中的自动变量,规则中的目标
$^: makefile中的自动变量,规则中的所有依赖
自动变量只能在规则的命令中使用(即第二行开始)
makefile中自己维护的变量(大写):
CC = cc(就是gcc)
CPPFlAGS = -Iobj=main.o add.o sub.o mul.o target=app $(target):$(obj) gcc $(obj) -o $(target) %.o:%.c gcc -c $< -o $@
-
工作原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zs5pFzKW-1634260075346)(C:\Users\wx\AppData\Roaming\Typora\typora-user-images\image-20210808153711387.png)]
4.函数
wildcard 查找目录中的所有符合条件的文件,并返回
pastsubst 将所有的.c 替换为.o,来源是第三个参数
target=app
src=$(wildcard ./*.c)
obj=$(patsubst ./%.c, ./%.o, $(src))
$(target):$(obj)
gcc $(obj) -o $(target)
%.o:%.c
gcc -c $< -o $@
.PHONY:clean
clean:
rm $(obj) $(target) -f
执行时,如果指定命令,则不执行其他命令,make clean,此时不会生成app,专门清空相关文件用。
-f 强制执行,不再进行错误提示。
.PHONY:clean: 声明clean为伪目标,不再查看是否目录下有clean文件否。
C库函数
fopen //返回文件指针 FILE* fp,结构体,包括文件描述符,文件读写指针位置,io缓存区
fclose
fread
fwrite
fgets
fputs
fscanf
fprintf
fseek //重置文件指针到开头位置
fgetc //读字符
fputc //写字符
ftell
feof
flush
-
文件描述符:索引到磁盘文件;
-
文件读写位置指针:读写文件过程中指针的实际位置,文件指针只有一个,读写文件时需要控制指针的位置
-
I/O缓冲区:内存地址,通过一个指针找到内存块,保存一些内容(纳秒级),这个内存大小一般8kb,当存到8k后,再一次性写到硬盘中;减少对硬盘的操作次数(对硬盘的操作速度比较慢,毫秒级);将缓存区内容刷到硬盘中的操作包括:
- fflush,强制将缓存区内容刷到硬盘中
- 缓存区已满
- 正常关闭文件:fclose;调用main中的return;调用main中的exit
-
linux系统没有缓存区,c库函数提供了缓存区
虚拟地址空间
linux每一个运行的程序(进程),操作系统都会为其分配一个0-4G(2的32次方)的地址空间(虚拟地址空间)(在硬盘中,不是占用实际物理内存4G,随着使用时,实际用了多少才会占用多少)。
文件描述符处于linux内核区部分(3-4G地址),这里有一个PCB进程控制块,PCB中有文件描述符表(数组,0-1023,每打开一个文件,占用一个文件描述符位置,而且使用的是空闲的最小的一个文件描述符),前三个文件描述符默认打开:STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO,标准输入、输出、错误。
当一个应用程序打开后,系统为程序(.out文件,linux下可执行文件格式ELF)分配一个虚拟地址空间,这里分为:
1. bss:未初始化全局变量,初值为0
2. data:已初始化全局变量
3. text:代码段,二进制机器指令
#define NULL (void*) 0
分配受保护的地址(0-4k);
局部变量存储到了栈空间,空间地址向下增长,即后分配的局部变量的地址比先分配局部变量的地址要小。
用户new的变量存储到了堆空间,空间地址向上增长,即后分配的地址比先分配的地址要大。
c标准库和linux系统io函数存放到共享库空间;动态库存放位置不定,通过偏移寻找对应函数。
静态库存放到了代码段空间,每次为应用程序分配代码段空间时,其位置不变。
命令行参数(int argc,char* argv[])存放到命令行参数空间。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HPPoUOWb-1634260075349)(C:\Users\wx\AppData\Roaming\Typora\typora-user-images\image-20210812090129183.png)]
之所以使用虚拟地址与物理地址的空间映射,是因为:
- 方便编译器和操作系统安排程序的地址分布。
可以使用一系列相邻的虚拟地址(比如用户需要使用的连续一段地址)访问屋里内存中不相邻的内存缓冲区(内存实际不能满足、或者没有相邻的空间地址时)。 - 方便进程之间的隔离
不用进程使用的虚拟地址彼此隔离,一个进程中的代码无法更改正在由另一进程使用的物理内存。 - 方便OS使用你那可怜的内存。
程序可以使用一系列虚拟地址访问物理内存的内存缓冲区,当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为4kb)保存到磁盘文件。数据或者代码页根据需要在物理内存和磁盘之间移动。
c库函数与系统函数之间的关系
FD:文件描述符
FP_POS:文件位置指针
BUFFER:文件缓存区
linux系统API:应用层(操作用户空间,转换到内核空间),系统调用(内核操作,调用设备驱动),内核层
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4k78tY3q-1634260075350)(C:\Users\wx\AppData\Roaming\Typora\typora-user-images\image-20210813084422848.png)]
系统IO函数
open
查看man文档的第二章,系统函数:man 2 open
头文件引用:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//文件路径,打开方式;返回一个文件描述符,错误时返回-1,并将错误赋值errno
int open(const char *pathname, int flags);
//创建文件方式打开文件:文件路径,打开方式(O_CREAT),给创建的文件访问权限(777那些)
int open(const char *pathname, int flags, mode_t mode);
errno:全局变量
1. 定义到头文件errno.h中,任何标准c库函数都能对其进行修改(linux系统函数更可以)
2. 错误宏定义位置:第1-34个错误:/user/include/asm-generic/errno-base.h; 第35-133个错误:/user/include/asm-generic/errno.h;
3. 是记录系统的最后一次错误代码。代码是一个int型的值;调用某些函数出错时,该函数会重新设置errno的值。
perror:函数
1. 头文件:stdio.h
2. 函数定义:void perror(const char *s)
3. 说明:用来将上一个函数发生错误的原因输出到标准设备(stderr);参数s所指的字符串会先打印出,后面再加上错误原因字符串; 此错误原因依照全局变量errno的值来决定要输出的字符串。
使用举例:
创建文件:vi myopen.c
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
//close头文件,unix标准头文件
#include<unistd.h>
//exit头文件
#include<stdlib.h>
//perror头文件
#include<stdio.h>
int main()
{
int fd;
//打开已经存在文件
fd = open("fileName",O_RDWR);
if(fd == -1)
{
perror("打开 fileName失败:");
exit(1);//退出当前进程
}
//创建新文件
fd = open("newfileName",O_RDWR | O_CREAT | O_EXCL, 0777);
if(fd == -1)
{
perror("新建 newfileName失败:");
exit(1);//退出当前进程
}
printf("fd = %d\n",fd);
//关闭文件
int ret = close(fd);
printf("ret = %d\n",ret);
if(ret == -1)
{
perror("关闭 fileName失败:");
exit(1);//退出当前进程
}
}
shift + k 在vi中打开对应函数的man文档
编译执行:gcc myopen.c -o myopen
执行:./myopen
查看本地掩码:umask;
文件的实际权限 = 需要给定的权限 位与操作 本地掩码(取反);
O_CREAT | O_EXCL
生成文件时判断是否已经存在;
O_TRUNC
将文件截断为0;操作之后,将文件内容清空;
read
查看man文档:man 2 read
头文件:#include <unistd.h>
函数定义:ssize_t read(int fd, void *buff, size_t count)
;有符号的返回值;文件描述符,文件缓冲区(一个数组),缓冲区大小;
返回值:
1. -1读文件失败
2. 0 文件读完了
3. 大于0,读取的字节数
write
查看man文档:man 2 write
头文件:#include <unistd.h>
函数定义:ssize_t write(int fd, conset void *buff, size_t count)
;有符号的返回值;文件描述符,文件缓冲区(一个数组),缓冲区大小;
read write读写文件
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
//打开一个已经存在的文件
int fd = open("en.txt",O_RDONLY);
if(fd == -1)
{
perror("read en.txt failed:");
exit(1);
}
//创建一个新的文件 用于写
int fd1 = open("newfile",O_CREAT | O_WRONLY, 0664);
if(fd1 == -1)
{
perror("creat failed");
exit(1);
}
//读文件
char buf[2048] = {0};
int count = read(fd,buf,sizeof(buf));
if(count == -1)
{
perror("read failed");
exit(1);
}
while(count)
{
//读出的文件写入另一个文件
int ret = write(fd1, buf, count);
printf("write bytes %d\n",ret);
count = read(fd,buf,sizeof(buf));
}
//关闭文件
close(fd);
close(fd1);
}
lseek
查看man文档:man 2 lseek
头文件:#include <unistd.h>
#include <sys/types.h>
函数定义:off_t lseek(int fd, off_t offset, int whence)
;int型的返回值;文件描述符,文件指针偏移量,三个值(SEEK_SET SEEK_CUR SEEK_END);
功能:获取文件大小;移动文件指针;文件拓展。
文件拓展后,文件里面的内容为空洞,无法打印出;占用文件大小。
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
int fd = open("aa",O_RDWR);
if(fd == -1)
{
perror("open error:");
exit(1);
}
int ret = lseek(fd, 0, SEEK_END);
printf("file length = %d\n",ret);
//只能向后拓展(扩大文件),增加2000字节的大小
int rett = lseek(fd, 2000, SEEK_END);
printf("return value = %d\n",rett);
//要实现文件拓展,需要进行一次写操作
write(fd, "a", 1);
close(fd);
return 0;
}
linux文件操作相关函数
stat 函数(获取文件属性信息)
int stat(const char* path, struct stat* buf);
buf为传出参数
stat结构体:
struct stat{
dev_t st_dev: //文件的设备编号
ino_t st_ino: //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬链接数,刚建立的文件为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //设备类型,如果文件为设备文件,则为其设备编号
off_t st_size; //文件字节数
blksize_t st_blksize; //块大小(文件系统的IO缓冲区大小)
blkcnt_t st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
}
st_mode: 两字节的十六位正整数;后12位存储权限(所有者,所属组,其他人)
文件类型:7种(S_IFIFO,S_IFCHR,S_IFDIR,S_IFBLK,S_IFREG,S_IFLNK,S_IFSOCK)占用前四位
(管道、字符设备、目录、块设备、普通文件、符号链接、套接字)
粘住位:设置后应用程序不会被顶到交换分区,很少使用;
穿透(追踪)函数:针对软连接文件,查看目标对应连接文件的大小(如命令vi)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<time.h>
#include<pwd.h>
#include<grp.h>
#include<unistd.h>
int main(int argc, char* argv[])
{
if(argc < 2)
{
printf("./a.out filename\n");
exit(1);
}
struct stat st;
int ret = stat(argv[1], &st);
if(ret == -1)
{
perror("stat:");
exit(1);
}
//存储文件类型和访问权限
char perms[11] = {0};
//判断文件类型 S_IFMT掩码01700
switch(st.st_mode & S_IFMT)
{
case S_IFLNK:
perms[0] = 'l';
break;
case S_IFDIR:
perms[0] = 'd';
break;
case S_IFREG:
perms[0] = '-';
break;
case S_IFBLK:
perms[0] = 'b';
break;
case S_IFCHR:
perms[0] = 'c';
break;
case S_IFSOCK:
perms[0] = 's';
break;
case S_IFIFO:
perms[0] = 'P';
break;
default:
perms[0] = '?';
break;
}
//判断文件的访问权限
//文件所有者
perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';
//文件所属组
perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';
//其他人
perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';
//硬链接计数
int linkNum = (int)(st.st_nlink);
//文件所有者
char* fileUser = getpwuid(st.st_uid)->pw_name;
//文件所属组
char* fileGrp = getgrgid(st.st_gid)->gr_name;
//文件大小
int fileSize = (int)(st.st_size);
//修改时间
char* time = ctime(&st.st_mtime);
char mtime[512] = {0};
strncpy(mtime, time, strlen(time)-1);
char buf[1024];
sprintf(buf,"%s %d %s %s %d %s %s",perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]);
printf("%s\n",buf);
return 0;
}
lstat函数
不追踪软连接;(相同命令如ls -l,rm)
access函数
作用:测试文件是否具有某种权限。
原型:int access(const char* pathname,int mode)
参数:文件名;权限类别(R_OK 是否有读权限,W_OK是否有写权限,X_OK是否有执行权限,F_OK文件是否存在)。
返回值:0,成功;-1,失败。
chmod函数
作用:修改 文件权限。
原型:int chmod(const char *path,mode_t mode);
int fchmode(int fd,mode_t mode);
参数:文件,权限(八进制数,如0775)
chown函数
作用:修改文件所有者
原型:int chown(const char* path, uid_t owner, gid_t group );
参数:路径,用户id,组ID(可在/etc/passwd中获取)
truncate函数
作用:将参数path指定的文件大小改为参数length指定的大小。如果原来的文件大小比参数length大,则超过的部分会被删去。文件被拓展后使用占位符。
原型:int truncate(const char *path, off_t length);
参数:路径,文件大小
链接函数
link函数
作用:创建一个硬链接
原型:int link(const char* oldpath, const char* newpath);
symlink函数
作用:创建一个软链接(符号链接)。
原型:
readlink函数
作用:读取链接对应的文件名,不是读内容。
原型:ssize_t reaklink(const char * path,char *buf, size_t bufsiz);
参数:buf存放读取的内容;只能读取软连接。
unlink函数
作用:删除一个文件的目录项并减少他的链接数,成功返回0,否则-1,错误的原因存于errno。如果想通过调用这个函数来成功删除文件,你就必须拥有这个文件的所属目录的写和执行权限。
使用:
-
如果是符号链接,则删除符号链接
-
如果是硬链接,硬链接数减1,当减为0时,释放数据块和inode
-
如果文件硬链接数为0,但有进行已经打开改文件,并持有文件描述符,则等进程关闭该文件时,kernel才真正去删除该文件。
原型:
int unlink(const char * pathname);
可以先打开文件,再执行unlink删除文件,将存在一个该文件的临时文件。
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
int ret = open("tempfile", O_CREAT | O_RDWR, 0664);
if(ret == -1){
perror("file open");
exit(1);
}
int fd = unlink("tempfile");
//write file
write(ret, "hello", 5);
//reset file pointer
lseek(fd,0,SEEK_SET);
//read file
char buf[24] = {0};
int len = read(fd,buf, sizeof(buf));
//print file
write(1,buf,len);
//close
close(fd);
return 0;
}
rename函数
c库函数,头文件stdio.h
int rename(const char* oldpath, const char* newpath);
linux目录操作相关函数
chdir
作用:修改当前进程路径
原型:int chdir(const char* path)
;
getcwd
作用:获取当前进程工作目录
原型:char *getcwd(char *buf, size_t size);
mkdir
作用:创建目录
注意:创建的目录需要执行权限,否则无法进入目录
原型:``int mkdir(const char *pathname, mode_t mode);
mode可为0777
rmdir
作用:删除一个空目录
原型:int rmdir(const char * pathname);
opendir
作用:打开一个目录
原型:DIR opendir(const char *name);
返回值:DIR结构指针,该结构是一个内部结构,保存所打开的信息,类似于FILE结构
readdir
作用:读目录
函数原型:struct dirent *readdir(DIR *dirp);
返回值:返回一条记录项
struct dirent
{
ino_t d_ino; //此目录进入点的inode
ff_t d_off; //目录文件开头至此目录进入点的位移
signed short int d_reclen; //d_name 的长度,不包含Null字符
unsigned char d_type; //d_name 所指的文件类型
har d_name[256]; //文件名
}
文件类型d_type:
1. DT_BLK - 块设备
2. DT_CHR - 字符设备
3. DT_DIR - 目录
4. DT_LNK - 软连接
5. DT_FIFO - 管道
6. DT_REG - 普通文件
7. DT_SOCK - 套接字
8. DT_UNKNOWN - 未知
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<fcntl.h>
#include<dirent.h>
#include<string.h>
int getFileNum(char* root)
{
DIR* dir = NULL;
dir = opendir(root);
if(dir == NULL)
{
perror("opendir");
exit(1);
}
struct dirent* ptr = NULL;
char path[1024] = {0};
int total = 0;
while( (ptr = readdir(dir))!=NULL )
{
//过滤. 和 ..
if(strcmp(ptr->d_name,".") == 0 || strcmp(ptr->d_name,"..") == 0)
{
continue;
}
//如果是目录
if(ptr->d_type == DT_DIR)
{
//递归目录
sprintf(path,"%s/%s",root,ptr->d_name);
total += getFileNum(path);
}
//普通文件
if(ptr->d_type == DT_REG)
{
total++;
}
}
closedir(dir);
return total;
}
int main(int argc, char* argv[])
{
if(argc < 2)
{
printf("./a.out dir\n");
exit(1);
}
int total = getFileNum(argv[1]);
printf("%s has file numbers %d\n",argv[1],total);
return 0;
}
closedir
作用:关闭目录
fcntl函数
作用: 改变已经打开的文件的属性
原型:
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
功能:复制一个现有的描述符 F_DUPFD
获得/设置文件描述符标记 F_GETFD/F_SETFD
获得/设置文件状态标记
-
F_GETFL
只读打开 O_RDONLY
只写打开 O_WRONLY
读写打开 O_RDWR
执行打开 O_EXEC
搜索打开目录 O_SEARCH
追加写 O_APPEND
非阻塞模式 O_NONBLOCK -
F_SETFL 可更改的几个标识 O_APPEND O_NONBLOCK
获得/设置一步IO所有权
获得/设置记录锁
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd;
int flag;
//测试字符串
char *p = "富强民主文明和谐自由平等公正法治";
char *q = "呵呵";
//只写的方式打开文件
fd = open("test.txt",O_WRONLY);
if(fd == -1)
{
perror("open");
exit(1);
}
//输入新的内容
if(write(fd, p, strlen(p)) == -1)
{
perror("write");
exit(1);
}
//使用F_GETFL 命令得到文件状态标志
flag = fcntl(fd, F_GETFL, 0);
if(flag == -1)
{
perror("fcntl");
exit(1);
}
//将文件状态标识添加 “追加写”
flag |= O_APPEND;
//将文件状态修改为追加写
if(fcntl(fd,F_SETFL, flag) == -1)
{
perror("fcntl append write");
exit(1);
}
//再次输入
if(write(fd, q, strlen(q))==-1)
{
perror("write2");
exit(1);
}
close(fd);
return 0;
}
dup dup2函数
作用: 复制现有的文件描述符
原型:int dup(int oldfd); 返回的是文件描述表中没有被占用的最小的文件描述符
int dup2(int oldfd, int newfd); 如果newfd已经被打开,则先把newfd关掉再复制;
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
int main()
{
int ret = open("tempfile.txt", O_RDWR);
if(ret == -1){
perror("file open");
exit(1);
}
printf("file open fd=%d\n",ret);
//找到进程文件描述表中 第一个 可用的文件描述符
//将参数指定的文件复制到该描述符后,返回这个描述符
int ret2 = dup(ret);
if(ret2 == -1)
{
perror("dup");
exit(1);
}
printf("dup fd = %d\n",ret2);
char* buf = "你是猴子的救兵吗\n";
char* buf2 = "我是程序猿";
write(ret, buf, strlen(buf));
write(ret2, buf2, strlen(buf2));
close(ret);
return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
int main()
{
int fd1 = open("tempfile.txt", O_RDWR);
if(fd1 == -1){
perror("file open");
exit(1);
}
int fd2 = open("newfile", O_RDWR);
if(fd2 == -1){
perror("file open2");
exit(1);
}
printf("file open fd1=%d\n",fd1);
printf("file open fd2=%d\n",fd2);
int ret2 = dup2(fd1, fd2);
if(ret2 == -1)
{
perror("dup2");
exit(1);
}
printf("dup fd2 = %d\n",fd2);
char* buf = "new language\n";
char* buf2 = "try again";
write(fd1, buf, strlen(buf));
write(fd2, buf2, strlen(buf2));
close(fd1);
close(fd2);
return 0;
}
拓展介绍
解决gcc编译过程中c99错误的问题
alias gcc='gcc -std=gnu99'
索引节点inode:保存的其实是实际的数据的一些信息,这些信息称为“元数据”(也就是对文件属性的描述)。例如:文件大小,设备标识符,用户标识符,用户组标识符,文件模式,扩展属性,文件读取和修改的时间戳,连接数量,指向存储该内容的磁盘区块的指针,文件分类等等。
(注意:数据分为:元数据 + 数据本身)
注意inode是怎样生成的:每个inode节点的大小,一般是128字节或者256字节。inode节点的总数,在格式化时就给定(现代os可以动态变化),一般每2KB就设置一个inode。一搬文件系统中很少有文件小于2KB的,所以预定按照2KB分,一般inode是用不完的。所以inode在文件系统安装的时候会有一个默认数量,后期会根据实际需求发生变化。
注意inode好:inode号是唯一的,标识不同的文件。其实在linux内部的时候,访问文件都是通过inode号进行,所谓文件名仅仅是给用户使用。当我们打开一个文件的时候,首先,系统找到这个文件对应的inode号;然后通过inode号,得到inode信息,最后,由inode找到文件数据所在的block,现在可以处理文件数据了。
inode和文件的关系:当创建一个文件的时候,就给文件分配了一个inode。一个inode只对应一个实际的文件,一个文件也会只有一个inode。inode最大数量就是文件的最大数量。
Review
-
gdb-- 命令
-
前提条件:可行性文件必须包括调试信息
gcc -g
-
gdb 文件名 – 启动gdb调试
-
查看代码的命令:当前文件(list 行号(函数名));指定文件(list 文件名:行号(函数名))
-
设置断点:当前文件(b 行号(函数名));指定文件(b 文件名:行号(函数名));条件断点:(b 行号 if 变量名==值);查看断点信息:info b;删除断点:d 断点的编号;
-
开始调试:
a. 只执行一行代码:start;
继续执行停在断点处:continue 或者 cb. 直接停在断点出:run或者r
-
单步调试:
a. 进入函数体:step 或者 s
跳出函数体:finish(如果在循环出有断点,需要删除该断点)b. 不进入函数体:next 或者 n
-
追踪变量:
自动打印变量值:display 变量名
取消变量追踪:undisplay 编号; 获取编号:info display
手动打印变量的值:print 或者 p 变量名
获取变量对应类型:ptype 变量名 -
跳出循环:u
-
退出gdb:quit
-
-
Makefile
-
一个规则
三要素:目标,依赖,命令
目标:依赖
命令- 第一条用于生成终极目标的规则
如果规则中的依赖不存在,向下寻找其他的规则。
更新机智:比较目标文件和依赖文件时间
- 第一条用于生成终极目标的规则
-
两个函数
- 查找指定目录下,指定类型的文件
src = $(wildcard ~/aa/*.c ) - 匹配替换函数
obj = $(patsubst %.c, %.o, $(src))
- 查找指定目录下,指定类型的文件
-
三个自动变量
-
$<:规则中的第一个依赖
-
$^:规则中的所有依赖
-
$@:规则中的目标
只能在规则的命令中使用
-
-
模式规则
%.o:%.c
gcc -c $< -o $@
-
-
Linux系统IO函数
-
文件描述符
int 类型
一个进程中最多可以打开:1024 - 3 -
pcb
进程控制块
文件描述符表,数组(大小1024)
选择数组中没有被占用的最小的一块使用 -
虚拟地址空间
用户去,内核区
代码段;已被初始化的全局变量;未被初始化的全局变量;堆(从下往上);共享库;栈(从上往下);环境变量; -
读取大文件 – 写入另一个文件
两种方式:
read write 每次读取一个byte
每次操作都与内核区交互getc putc 每次读取一个byte (效率高)
标准c库函数,内部有一个缓冲区,读取文件后先放进了内存缓冲区,再操作内核区
-