linux学习笔记--基础

gcc编译过程

hello.c  --> 预编译 -->
需要安装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:增加调试信息

静态库

  1. 命名规则:lib + 库的名字 + .a
  2. 制作过程:
    - 生成对应的 .o文件:gcc -c命令 ;如:gcc *.c -c -I…/include (将当前目录中的所有.c文件进行汇编,默认生成名字,头文件在上一级目录的include目录内)
    - 将生成的 .o文件打包:ar rcs 生成的静态库的名字 生成的所有的.o ;如:ar rcs libMyCalc.a *.o
  3. 发布和使用静态库
    - 发布静态库
    - 头文件

一般文件结构可以列为:
在这里插入图片描述
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的地址空间(虚拟地址空间),虚拟内存占用硬盘空间:其分配如下:
在这里插入图片描述

  1. 命名规则: lib + 库名字 + .so

  2. 制作步骤:

    • 生成与位置无关的代码(.o)

        gcc  -fPIC -c *.c -I../include
      
    • 将.o打包为共享库(动态库)

        gcc -shared -o libMyCalc.so *.o  -I../include
      
  3. 发布和使用共享库
    在这里插入图片描述

    • 制作完成后,可以将 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 directory

     ldd  myapp      查看可执行程序(myapp)在执行的时候依赖的所有的动态库
    

    在这里插入图片描述

  4. 解决程序执行时动态库无法被加载的问题
    程序执行的时候会根据环境变量去查找动态库,其过程如下:
    在这里插入图片描述
      由于当前动态库不在系统的/lib目录中,为了找到动态库,需要配置环境变量:LD_LIBRARY_PATH,之后调用会先查找此目录中的动态库。

     export LD_LIBRARY_PATH=../lib            
     临时测试动态库的环境变量设置,关闭终端后无效其中路径为动态库所在相对路径,相对当前输入命令时所处的目录
    
    • 配置文件.bashrc 最后一行增加绝对目录(动态库所在路径)

    • 找到动态连接器的配置文件(/etc/ld.so.conf),将动态库的路径写到里面,再更新配置

        sudo ldconfig -v
      
  5. 优缺点
    使用动态库时,打包应用程序时,并没有将库打包到程序中,只是做了一个标记,运行后,相关的库才加载到内存中。
    **优点:**执行程序包体积小;动态库更新后(接口不变),不需要重新编译应用程序(如上main.c),需要把动态库提供给用户。
    **缺点:**动态库没有打包到应用程序中,加载起来相对较慢。

Windows的库

​ **dll文件:**它是应用程序调用dll运行时,真正的可执行文件。dll应用在编译、链接成功后,.dll文件即存在。开发成功后的应用程序在发布时,只需要有.exe文件和.dll文件,不必有.lib文件和dll头文件。

dll的引入库文件:(即动态库的lib) 它是dll在编译、链接成功后生成的文件。主要作用是当其它应用程序调用dll时,需要将该文件引入应用程序。否则,dll无法引入。

  1. lib是编译时需要的,dll是运行时需要的(动态库文件,vs动态库包含lib和dll文件)。
    如果要完成源代码的编译,有lib就够了。
    如果也使动态连接的程序运行起来,有dll就够了。
    在开发和调试阶段,当然最好都有。
  2. 一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。
  3. 在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。

​ 动态链接库 (DLL) 是作为共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。
​ 动态链接与静态链接的不同之处在于:动态链接允许可执行模块(.dll 文件或 .exe 文件)仅包含在运行时定位 DLL 函数的可执行代码所需的信息。在静态链接中,链接器从静态链接库获取所有被引用的函数,并将库同代码一起放到可执行文件中。
​ 使用动态链接代替静态链接有若干优点。DLL 节省内存,减少交换操作,节省磁盘空间,更易于升级,提供售后支持,提供扩展 MFC 库类的机制,支持多语言程序,并使国际版本的创建轻松完成。

GCC

gcc工作流程

  1. 预处理 gcc -E

  2. 编译 gcc -S 最耗时

  3. 汇编 gcc -c

  4. 链接 没有参数

    -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))

制作动态库

  1. 动态库后缀:.so 命名:libMyName.so
  2. 步骤:
    1. 生成与位置无关的代码.o gcc -fPIC -c *.c -Iinclude
    2. 将.o打包生成.so文件 gcc -shared -o libMyTest.so *.o
  3. 使用:main.c(引用的文件) lib(库目录) include(头文件目录)
    gcc main.c -Llib -lMyTest -Iinclude -o app
  4. 应用程序不能执行,动态链接器链接不到自己制作的库
    1. 临时设置方法:
      linux中有一个环境变量,将连接器目录导入该变量export LD_LIABRARY_PATH = ./lib;终端关闭后失效
    2. 永久设置方法:
      1.找到动态链接库的配置文件:/etc/ld.so.conf
      2.在该文件中添加动态库的目录(绝对路径)
      3.更新:sudo ldconfig -v(-v查看配置信息)

gbd调试

​ 先生成可执行文件: gcc *.c -o app -g 使用-g带调试信息,生成app可执行文件,执行文件后可以看到打印信息。可以通过ls -l查看带有调试信息的可执行文件的大小要比普通文件大。

  1. 启动gdb,进入gdb模式:gdb app
  2. 在gdb模式查看源代码: l
  3. 查看指定文件(:行数):l file.c:20
  4. 查看指定文件里的函数:l file.c:selectionSort 继续输入命令l,列出该函数之后的内容,之后可以输入回车,重复上一步的操作。
  5. 指定行打断点:break 22 或者 b 22b file.c:20
  6. 条件断点:b 12 if i==15 只有当i等于15时,12行的断点才触发(只能停在for循环内部的行数)
  7. 查看断点: info break 或者 i b
  8. 开始调试: start (只执行一步)或者 run
  9. 单步调试:n(next)
  10. 执行到下一断点:c(continue)
  11. 进入函数体:s(step)
  12. 查看函数体内的源代码:l(list)
  13. 查看断点停留处变量(j)的值:p j(print)
  14. 查看变量类型:ptype j
  15. 跟踪变量的值:display i跟踪变量i的值,接下来每次调试下一步都会展示变量 i 的值
  16. 取消跟踪变量:undisplay 1(变量编号,通过info display查看)
  17. 跳出循环: u
  18. 从进入到的函数体内部回到进入位置:finish
  19. 删除断点:delete 断点对应编号(Num) 或者缩写 d 断点对应编号(Num)
  20. 设置变量值(循环中跳到变量值为某个数的循环中):set var i=10
  21. 退出gdb:quit

makefile

当项目文件较多,目录很大时,gcc命令就会显复杂和难以使用。

  1. makefile文件的名字一般全部使用小写字母或者只有首字母大写。

  2. 规则

    ​ 三要素:目标(生成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 = -I

    obj=main.o add.o sub.o mul.o
    target=app
    $(target):$(obj)
    	gcc $(obj) -o $(target)
    
    %.o:%.c
    	gcc -c $< -o $@
    
  3. 工作原理:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]

之所以使用虚拟地址与物理地址的空间映射,是因为:

  1. 方便编译器和操作系统安排程序的地址分布。
    可以使用一系列相邻的虚拟地址(比如用户需要使用的连续一段地址)访问屋里内存中不相邻的内存缓冲区(内存实际不能满足、或者没有相邻的空间地址时)。
  2. 方便进程之间的隔离
    不用进程使用的虚拟地址彼此隔离,一个进程中的代码无法更改正在由另一进程使用的物理内存。
  3. 方便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. 如果是符号链接,则删除符号链接

  2. 如果是硬链接,硬链接数减1,当减为0时,释放数据块和inode

  3. 如果文件硬链接数为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

​ 获得/设置文件状态标记

  1. F_GETFL
    只读打开 O_RDONLY
    只写打开 O_WRONLY
    读写打开 O_RDWR
    执行打开 O_EXEC
    搜索打开目录 O_SEARCH
    追加写 O_APPEND
    非阻塞模式 O_NONBLOCK

  2. 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

  1. gdb-- 命令

    1. 前提条件:可行性文件必须包括调试信息gcc -g

    2. gdb 文件名 – 启动gdb调试

    3. 查看代码的命令:当前文件(list 行号(函数名));指定文件(list 文件名:行号(函数名))

    4. 设置断点:当前文件(b 行号(函数名));指定文件(b 文件名:行号(函数名));条件断点:(b 行号 if 变量名==值);查看断点信息:info b;删除断点:d 断点的编号;

    5. 开始调试:
      a. 只执行一行代码:start;
      继续执行停在断点处:continue 或者 c

      b. 直接停在断点出:run或者r

    6. 单步调试:
      a. 进入函数体:step 或者 s
      跳出函数体:finish(如果在循环出有断点,需要删除该断点)

      b. 不进入函数体:next 或者 n

    7. 追踪变量:
      自动打印变量值:display 变量名
      取消变量追踪:undisplay 编号; 获取编号:info display
      手动打印变量的值:print 或者 p 变量名
      获取变量对应类型:ptype 变量名

    8. 跳出循环:u

    9. 退出gdb:quit

  2. Makefile

    1. 一个规则
      三要素:目标,依赖,命令
      目标:依赖
      命令

      1. 第一条用于生成终极目标的规则
        如果规则中的依赖不存在,向下寻找其他的规则。
        更新机智:比较目标文件和依赖文件时间
    2. 两个函数

      1. 查找指定目录下,指定类型的文件
        src = $(wildcard ~/aa/*.c )
      2. 匹配替换函数
        obj = $(patsubst %.c, %.o, $(src))
    3. 三个自动变量

      1. $<:规则中的第一个依赖

      2. $^:规则中的所有依赖

      3. $@:规则中的目标

        只能在规则的命令中使用

    4. 模式规则
      %.o:%.c
      gcc -c $< -o $@

  3. Linux系统IO函数

    1. 文件描述符
      int 类型
      一个进程中最多可以打开:1024 - 3

    2. pcb
      进程控制块
      文件描述符表,数组(大小1024)
      选择数组中没有被占用的最小的一块使用

    3. 虚拟地址空间
      用户去,内核区
      代码段;已被初始化的全局变量;未被初始化的全局变量;堆(从下往上);共享库;栈(从上往下);环境变量;

    4. 读取大文件 – 写入另一个文件
      两种方式:
      read write 每次读取一个byte
      每次操作都与内核区交互

      getc putc 每次读取一个byte (效率高)
      标准c库函数,内部有一个缓冲区,读取文件后先放进了内存缓冲区,再操作内核区

文化建设

登金陵凤凰台
凤凰台上凤凰游,凤去台空江自流
吴宫花草埋幽径,晋代衣冠成古丘
三山半落青天外,二水中分白鹭洲
总为浮云能蔽日,长安不见使人愁
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
B站上的韩顺平老师的《Linux学习笔记》系列课程非常值得推荐。通过这个课程,我学到了很多关于Linux操作系统的知识和技能。 首先,韩老师在课程中详细介绍了Linux的基本概念和特点。我清楚地了解到Linux是一个开源的操作系统,具有稳定性、安全性和可定制性强的特点。这让我对Linux有了更深入的理解,也更有信心去学习和使用它。 其次,韩老师从基础开始,逐步讲解了Linux的安装和配置。他用简单明了的语言和实际操作的示范,帮助我了解了如何在虚拟机上安装Linux系统,并设置网络、用户账户、文件系统等。这为我后续的学习和实践打下了坚实的基础。 此外,韩老师还讲解了Linux的常用命令和工具。他详细介绍了常用的文件和目录操作命令,比如cd、ls、mkdir、cp等。同时,他还讲解了grep、sed、awk等强大的文本处理工具的使用方法。这些内容帮助我更加高效地进行文件管理和数据处理。 最后,韩老师还介绍了Linux的网络管理和安全防护。他讲解了如何配置网络连接、使用ssh远程登录以及设置防火墙等内容。这些知识对我了解网络和保护系统安全非常有帮助。 总的来说,韩顺平老师的《Linux学习笔记》课程非常实用,对于初学者来说是入门学习Linux的好选择。他通过深入浅出的讲解和丰富的实操示范,让我可以轻松地学习Linux的基本知识和操作技巧。我相信通过学习这个课程,我会在Linux领域有更进一步的发展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值