Linux -- 基础IO(2)

简介: 1. 基础认识文件=内容+属性,对文件的操作无非就两种:对内容进行操作或者对属性进行操作。文件没有被操作的时候,文件一般是放在磁盘上的。文件被操作的时候,文件是内存中的,因为是冯诺依曼体系的规定。文件被操作的时候,文件被加载内存中的必须得有文件属性。打开文件本质就是将需要的文件属性加载到内存中,操作系统一定同时存在大量的被打开的文件,同时操作系统也要管理这些被打开的文件,要管理文件就需要先描述后组织,先描述就是构建在内存中的文件结构体struct file{},这个结构体可以从磁盘上拿到,后组织就是通过数据结构来组织,比如:链表来连接结构体节点。文件被分为两大类:磁盘文件、内存文件

4. 理解文件缓冲区

带着疑惑看下面:输出缓冲区和我们上面的文件struct file中的缓冲区是一样的吗?

4.1 现象

jyh@VM-12-12-centos buffer]$ ll
total 8
-rw-rw-r-- 1 jyh jyh 218 Mar 21 15:30 buf.c
-rw-rw-r-- 1 jyh jyh  55 Mar 21 15:28 Makefile
[jyh@VM-12-12-centos buffer]$ cat Makefile 
buf:buf.c
  gcc -o $@ $^
.PHONY:clean
clean:
  rm -f buf
[jyh@VM-12-12-centos buffer]$ cat buf.c 
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
  fprintf(stdout, "hello  Linux\n");
  const char* message = "how are you?\n";
  write(1, message, strlen(message));
  fork();
  return 0;
}
[jyh@VM-12-12-centos buffer]$ make
gcc -o buf buf.c
[jyh@VM-12-12-centos buffer]$ ls
buf  buf.c  Makefile
[jyh@VM-12-12-centos buffer]$ ./buf 
hello  Linux
how are you?
[jyh@VM-12-12-centos buffer]$ ./buf  > test.txt
[jyh@VM-12-12-centos buffer]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8536 Mar 21 15:32 buf
-rw-rw-r-- 1 jyh jyh  218 Mar 21 15:30 buf.c
-rw-rw-r-- 1 jyh jyh   55 Mar 21 15:28 Makefile
-rw-rw-r-- 1 jyh jyh   39 Mar 21 15:32 test.txt
[jyh@VM-12-12-centos buffer]$ cat test.txt 
how are you?
hello  Linux
hello  Linux
[jyh@VM-12-12-centos buffer]$ 

这里为什么调用重定向写入到test.txt中就有两个hello Linux被输出呢?为什么会出现这种现象呢?下面先认识文件缓冲区。

4.2 文件缓冲区

微信图片_20230524015304.png

./buf在执行fprintf()函数时,此时C语言库中有对应的缓冲区,先把字符串写入到C语言库中的缓冲区,当进程执行起来时就创建了文件描述符表,和文件对象产生映射关系,然后通过映射关系找到对应的文件对象,再讲C语言库中的字符串拷贝到文件对象的文件缓冲区中。所以通过这个例子就可以说明上述的现象:因为"hello Linux"字符串是fprintf()中C语言库的,创建子进程会再拷贝一份代码给子进程,但是write是系统调用是直接写入到文件缓冲区的,并不是通过C语言库缓冲区再到文件缓冲区的。


补充:三种刷新策略:1.无缓冲(不会刷新到缓冲区中,而是直接给操作系统)2.行缓冲(遇到\n终止刷新)3.全缓冲(只有缓冲区写满才会刷新);显示器采用的刷新策略是行缓冲,普通文件采用的是全缓冲;为什么要有缓冲区?节省调用时间。缓冲区在哪里呢?fopen()打开文件时,会有FILE结构体,缓冲区就在其FILE结构体中。

4.3 C接口和系统调用模拟

mystdio.h文件

#pragma once
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#define N 1024
#define BUFFER_NONE 0x1
#define BUFFER_LINE 0x2 //行缓冲
#define BUFFER_ALL  0x4 //全缓冲
typedef struct MY_FILE
{
  int fileDescriptor;
  char outputBuffer[N];
  int flags; //刷新方式
  int current; //写入位置
}MY_FILE;
MY_FILE* my_fopen(const char* path, const char* mode); //对应的是fopen()-c接口
size_t my_fwrite(const void* ptr, size_t size, size_t nmemb, MY_FILE* stream); //对应的是fwrite()-c接口
int my_fclose(MY_FILE* fp); //对应的是fclose()-c接口

mystdio.c文件

#include "mystdio.h"
int my_fflush(MY_FILE* fp)
{
  assert(fp);
  write(fp->fileDescriptor, fp->outputBuffer, fp->current);
  fp->current = 0;
  fsync(fp->fileDescriptor); //同步文件的核内状态到存储设备
  return 0;
}
MY_FILE* my_fopen(const char* path, const char* mode) //对应的是fopen()-c接口
{
  //1.辨别打开模式
  int flag = 0;
  if(strcmp(mode, "r") == 0) flag |= O_RDONLY;
  else if(strcmp(mode, "w") == 0) flag |= (O_WRONLY | O_CREAT | O_TRUNC);
  else if(strcmp(mode, "a") == 0) flag |= (O_WRONLY | O_CREAT | O_APPEND);
  // 其他方式就不列举
  //2.设置默认mode
  mode_t m = 0664;
  //3.调用open()系统调
  int fd = 0;
  if(flag & O_CREAT) fd = open(path, flag, m);
  else fd = open(path, flag);
  //4.调用open()失败
  if(fd < 0) return NULL;
  //5.调用open()成功返回MY_FILE结构体指针
  MY_FILE* my_file = (MY_FILE*)malloc(sizeof(MY_FILE));
  if(my_file == NULL) //调用malloc()失败
  {
    close(fd); //关闭文件防止对后续操作受影响
    return NULL;
  } 
  my_file->fileDescriptor = fd;
  my_file->flags = 0;
  my_file->flags |= BUFFER_LINE; //默认采用行刷新
  my_file->current = 0;
  memset(my_file->outputBuffer, '\0', sizeof(my_file->outputBuffer));
  return my_file;
}
size_t my_fwrite(const void* ptr, size_t size, size_t nmemb, MY_FILE* stream)
{
  //1. 缓冲区已满直接写入 
  if(stream->current == N) my_fflush(stream); 
  //2. 缓冲区未满,数据拷贝,更新写入位置
  size_t fill_sz = size * nmemb;
  size_t have_sz = N - stream->current;
  size_t write_sz = 0;
  if(have_sz >= fill_sz) 
  {
    memcpy(stream->outputBuffer + stream->current, ptr, fill_sz);
    stream->current += fill_sz;
    write_sz = fill_sz;
  }
  else 
  {
    memcpy(stream->outputBuffer + stream->current, ptr, have_sz);
    stream->current += have_sz;
    write_sz = have_sz;
  }
  //3. 刷新
  if(stream->flags & BUFFER_LINE) 
  {
    if((stream->outputBuffer[stream->current - 1]) == '\n') my_fflush(stream);
  }
  else if(stream->flags & BUFFER_ALL) 
  {
    if(stream->current == N) my_fflush(stream);
  } 
  return write_sz;
}
int my_fclose(MY_FILE* fp)
{
  assert(fp);
  //1. 刷新缓冲区
  if(fp->current > 0) my_fflush(fp); 
  //2. 关闭文件
  close(fp->fileDescriptor);
  //3. 释放空间
  free(fp);
  fp = NULL;
  return 0; //操作成功
}

test.c文件

#include "mystdio.h"
int main()
{
  MY_FILE* fp = my_fopen("log.txt", "w");
  if(fp == NULL) exit(1);
  const char* message = "hello linux";
  int cnt = 5;
  while(cnt--)
  {
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "%s, %d\n", message, cnt);
    size_t size = my_fwrite(buffer, strlen(buffer), 1, fp);
    sleep(1);
    printf("当前成功写入: %zd个字节\n", size);
  }
  my_fclose(fp);
  return 0;
}


总结

不发生刷新的本质是不调用系统调用,并没有刷新到文件中,而是放在了对应的FILE结构体中的缓冲区中,因此,fwrite()函数调用会非常快

可以在缓冲区中挤压多份数据,随后统一进行刷新。本质:一次IO可以IO更多的数据,提高IO效率

fflush刷新的本质就是将结构体中的缓冲区也就是用户缓冲区中的数据通过系统调用接口写入到操作系统


5. 磁盘

5.1 磁盘物理组成

聊到磁盘文件就需要了解磁盘:

image.png

磁盘是有许多盘片、机械手臂、磁头、主轴马达、接口插座、控制电路所组成。

实际运行时,主轴马达让盘片转动,然后机械手臂可伸展让磁头在盘片上进行读写操作(这里读写操作对应就是寻找CHS地址,也就是sector(扇区)、header(磁头)、cylinder(柱面)这三者先是磁头找到对应的柱面在找到对应的扇区)。

磁盘的最小存储单位是扇区,在物理组成分面,每个扇区的大小是512字节。

所有盘片上的同一磁道可以组成一个柱面,分割硬盘的最小单位就是柱面。


磁盘三维结构是这样的(对应上面3、4条来看):

image.png再进一步了解之前先想一想一个问题:操作系统内部会直接使用CHS地址来进行访问修改数据吗?如果不会为什么呢?


不会直接使用CHS地址,因为操作系统是软件,而磁盘是硬件,硬件会持续更新改变,如果操作系统也需要持续改变,这样就会发生耦合,所以要对其解耦操作就不需要持续改变;另外扇区是512字节,硬件的基本单位是4KB(可以调整),所以操作系统有一套自己的地址对其进行块级别访问修改等操作。


5.2 磁盘分区表

盘片中有很多扇区,但是每个扇区都一样重要吗?并不是,磁盘的第一个扇区特别重要了,主要记录主引导分区(Master Boot Record, MBR):可以安装引导加载程序的地方,有446字节;以及分区表(partition table):记录整块硬盘分区的状态,有64字节。这里我们来谈分区表:分区表是什么呢?把硬盘比作一根原木,需要把这个原木分割为很多区段才能做成满意的家具,如果不分割原木不能有效利用,同样的道理硬盘需要分区才能被你充分利用。分区表中的64字节容量中,总共分为四组记录区,每组记录区记录了该区段的启动和结束柱面号码。

微信图片_20230524021024.png

上图中假设硬盘有400个柱面,共分为四个分区,第四个分区为第301到400号柱面的范围,当你使用Windows操作系统第一到第四个分区应该就是C,D,E,F,那么第301到400号柱面就是对应的F盘。由于分区表只有64自己而已,最多只能容纳四个分区,这四个分区被称为主或扩展分区。重要信息:其实所谓的分区只是针对64字节的分区表进行设置而已;硬盘默认的分区表仅仅能写人四组分区信息;分区的最小单位是柱面。


为什么要分区?

分区数据是分开的,那么当你需要将某个分区的数据重整时,例如:你要重新安装Windows时,可以将C盘张其他重要数据移动到其他分区,这样就不会丢失数据,所以善用分区,可以让数据更安全。

按照区段进行搜索数据时,这样有助于提高系统的性能和速度。


分区表就只能把一块硬盘发最多分为四个分区吗?不是的。

微信图片_20230524021118.png

L1-L5这五个由扩展分区切出来的分区称为逻辑分区。重要信息:主分区和扩展区最多可以有四个;扩展分区最多只能有一个;逻辑分区有扩展分区持续切割出来的分区;能够被格式化后作为数据访问的分区是主分区和逻辑分区,扩展分区不能被格式化;逻辑分区的数量依操作系统而不同,IDE硬盘最多有59个逻辑分区(5到63号),SATA硬盘有11个逻辑分区(5到15号)。


6. 文件系统

6.1 文件系统特性

磁盘分区后需要进行格式化,为什么需要格式化呢?每种操作系统文件系统所设置的文件属性/权限并不相同,为了存放这些文件所需要的数据,因此需要对其格式化。提一句:Linux的正规文件系统是Ext2。传统磁盘和文件系统应用中,一个分区就是只能被格式化成一个文件系统,所以说一个文件系统就是一个分区,但是由于新技术的应用可以将一个分区格式化为多个文件系统,也能将多个分区合成一个文件系统,所以可以称呼一个被挂载的数据为一个文件系统而不是一个分区。那么文件系统是如何运行的呢?文件包含内容和属性,那么文件系统通常会将内容和属性这两部分的数据分别存在不同的块中,权限和属性放在inode中,内容放在data block块中。另外还有一个super block会记录文件系统的整个信息,包括inode和block的总量、使用量等等。每一个inode和block 都有编号方便管理。其中inode记录文件属性,一个文件占用一个inode,同时记录此文件的数据所在的data block号;block是记录文件内容,如果文件太大时,会占用多个block。既然我们知道inode中包含data block号,那么也就是说只要找到文件的inode就可以通过inode中data block编号读写数据,性能比较好。就像下图:

微信图片_20230524021230.png

6.2 Linux的Ext2文件系统(inode)

Linux文件系统Ext2就是使用inode为基础的文件系统。inode是记录权限和属性,data block是记录文件内容。文件系统一开始就将inode和block规划好了,除非重新格式化,否则inode和block固定后就不再变动。如果仔细考虑一下,如果文件系统高达数百GB时,将所有的inode和block放置在一起很不明智,因此Ext2文件系统在格式化的时候基本上就区分为多个快组,每个块组就有独立的inode/block/superblock系统。

微信图片_20230524021328.png

data block(数据块):Ext2文件系统中所支持的block大小有1KB、2KB、4KB三种而已。重要的信息:原则上,block大小和数量在格式化后就不能够改变(除非重新格式化);每个block内最多只能够放置一个文件数据,如果文件大于block大小,一个文件会占用多个block,如果文件小于block则block剩余空间不能够再被使用(磁盘空间会浪费)。这里有个问题如果文件都很小,但是你的block在格式化却选用4KB,可就产生很大空间浪费,假设使用4KBblock,有10000个小文件,每个文件50字节,磁盘浪费多少?(4096 - 50)* 10000 = 38.6MB,但是文件容量是50*10000=448.3KB,这里就产生了很大的磁盘容量浪费,但是可不可用1KBblock这样就可以浪费少,但是这里会读写性能下降,所以block容量尽量选择大的,默认的block就是4KB。


inodetable(inode表格):表格中可能有权限和属性信息,也会有block编号;每个inode大小固定为128字节,每个文件都仅仅会占用一个inode而已,因此文件系统能创建的文件数量和inode数量相关。


下面来分析一下inode/block和文件大小的关系,inode记录的数据很多,但是只有128字节大小,而记录一个block就需要4个字节。假设一个文件有400MB,每个block为4KB时,就有至少10万条block号码,inode128字节此时怎么来记录呢?系统采用12个直接索引、1个间接索引、1个双间接索引、1个三间接索引记录区。具体这记录索引方式是什么呢?

微信图片_20230524021427.png

super block(超级块):记录信息有:block和inode总量、使用量、剩余量,block和inode大小,文件系统挂载时间等等。superblock大小是1024字节。


group descriptor table(文件系统描述表):描述每个block group的开始和结束的block号码,以及说明每个区段分别介于那个block号码之间。


block bitmap(块对照表):就是一个位图用来记录是否存在该block,每个block有4KB,这个就有4096*8 个比特位,说明可以记录这么多个block的存在状态。


inode bitmap(inode对照表):就是一个位图用来记录是否存在该inode,每个inode有128字节,这个就有128*8 个比特位,说明可以记录这么多个inode的存在状态。

6.3 抽象理解

我们知道了磁盘的结构,下面以单个盘片来说,磁道上的扇区全部拿下来就变成了一个数组,第一个扇区中有主引导分区和一个分区表,八个扇区也就是一个block(这样更方便管理),块中存放数据:

微信图片_20230524021509.png

管理区,对区再把块分组,再对每个块组做管理:

微信图片_20230524021542.png

6.4 查看文件系统

dumpe2fs [-bh] 设备文件名

作用:查看每个区段和superblock相关的信息

[root@VM-12-12-centos jyh]# df  //调出目前挂载的设备
Filesystem     1K-blocks    Used Available Use% Mounted on
devtmpfs         1012336       0   1012336   0% /dev
tmpfs            1023340      24   1023316   1% /dev/shm
tmpfs            1023340     576   1022764   1% /run
tmpfs            1023340       0   1023340   0% /sys/fs/cgroup
/dev/vda1       41152716 9614720  29760172  25% /
tmpfs             204668       0    204668   0% /run/user/0
tmpfs             204668       0    204668   0% /run/user/1002
[root@VM-12-12-centos jyh]# dumpe2fs /dev/vda1
dumpe2fs 1.42.9 (28-Dec-2013)
Filesystem volume name:   <none> //文件系统名称
Last mounted on:          /
Filesystem UUID:          4b499d76-769a-40a0-93dc-4a31a59add28
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index filetype needs_recovery extent 64bit flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
Filesystem flags:         signed_directory_hash 
Default mount options:    user_xattr acl
Filesystem state:         clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              2621440
Block count:              10485499
Reserved block count:     440360
Free blocks:              8157120
Free inodes:              2520067
First block:              0
Block size:               4096 //block size
Fragment size:            4096
Group descriptor size:    64
Reserved GDT blocks:      1019
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         8192
Inode blocks per group:   512
Flex block group size:    16
Filesystem created:       Thu Mar  7 14:38:36 2019
Last mount time:          Wed Feb 15 20:35:56 2023
Last write time:          Wed Feb 15 20:35:53 2023
Mount count:              50
Maximum mount count:      -1
Last checked:             Thu Mar  7 14:38:36 2019
Check interval:           0 (<none>)
Lifetime writes:          462 GB
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:           256      //inode size
Required extra isize:     28
Desired extra isize:      28
Journal inode:            8
First orphan inode:       25570
Default directory hash:   half_md4
Directory Hash Seed:      58cbc593-e8e9-4c19-9dcf-645326b54c80
Journal backup:           inode blocks
Journal features:         journal_incompat_revoke journal_64bit
Journal size:             128M
Journal length:           32768
Journal sequence:         0x00793319
Journal start:            3001

7. 软硬连接

7.1 现象

[root@VM-12-12-centos jyh]# ll
total 12
-rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
-rw-r--r--  1 root root   36 Mar 31 22:49 myfile.txt
[root@VM-12-12-centos jyh]# cat myfile.txt 
hello linux
hello linux
hello linux
[root@VM-12-12-centos jyh]# ln -s myfile.txt my-soft //软链接命令
[root@VM-12-12-centos jyh]# ll
total 12
-rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
-rw-r--r--  1 root root   36 Mar 31 22:49 myfile.txt
lrwxrwxrwx  1 root root   10 Mar 31 22:49 my-soft -> myfile.txt
[root@VM-12-12-centos jyh]# cat my-soft 
hello linux
hello linux
hello linux
[root@VM-12-12-centos jyh]# ls -il
total 12
919566 -rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
790033 drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
790021 -rw-r--r--  1 root root   36 Mar 31 22:49 myfile.txt
794519 lrwxrwxrwx  1 root root   10 Mar 31 22:49 my-soft -> myfile.txt
[root@VM-12-12-centos jyh]# 
[root@VM-12-12-centos jyh]# ln myfile.txt my-hard //硬链接命令
[root@VM-12-12-centos jyh]# ls -il
total 16
919566 -rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
790033 drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
790021 -rw-r--r--  2 root root   36 Mar 31 22:49 myfile.txt 
790021 -rw-r--r--  2 root root   36 Mar 31 22:49 my-hard
794519 lrwxrwxrwx  1 root root   10 Mar 31 22:49 my-soft -> myfile.txt
[root@VM-12-12-centos jyh]# 

软链接是一个独立的连接文件,有自己的inode号,就有自己的属性。硬链接和目标文件公用同一个inode,也就是硬链接一定和目标文件使用用一个inode,硬链接本质就是建立了新的文件和inode的映射关系。

image.png

这个myfile.txt文件对应的有一个inode,inode中就会有链接数变量用来记录链接数,硬链接后my-hard和myfile.txt文件的inode一样,使得此inode中的链接数进行++操作,变成了2,如果删除myfile.txt链接数变为1:

[root@VM-12-12-centos jyh]# ll
total 16
-rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
-rw-r--r--  2 root root   36 Mar 31 22:49 myfile.txt
-rw-r--r--  2 root root   36 Mar 31 22:49 my-hard
lrwxrwxrwx  1 root root   10 Mar 31 22:49 my-soft -> myfile.txt
[root@VM-12-12-centos jyh]# rm -f myfile.txt 
[root@VM-12-12-centos jyh]# ls -il
total 12
919566 -rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
790033 drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
790021 -rw-r--r--  1 root root   36 Mar 31 22:49 my-hard //链接数变成1 
794519 lrwxrwxrwx  1 root root   10 Mar 31 22:49 my-soft -> myfile.txt //软连接不能使用
[root@VM-12-12-centos jyh]# 

7.2 软连接初步使用场景

//软链接:当一个文件层次太深时,可以直接软连接在当前目录就可以对其相关操作
[root@VM-12-12-centos test4]# ll
total 4
drwxr-xr-x 3 root root 4096 Mar 31 23:10 d1
lrwxrwxrwx 1 root root   21 Mar 31 23:13 exe-soft -> d1/d2/d3/d4/d5/d6/exe
[root@VM-12-12-centos test4]# tree
.
|-- d1
|   `-- d2
|       `-- d3
|           `-- d4
|               `-- d5
|                   `-- d6
|                       |-- exe
|                       `-- test.cc
`-- exe-soft -> d1/d2/d3/d4/d5/d6/exe
6 directories, 3 files
[root@VM-12-12-centos test4]# ./d1/d2/d3/d4/d5/d6/exe 
softLink sccuess 
[root@VM-12-12-centos test4]# ./exe-soft  //使用软连接更快捷
softLink sccuess 
[root@VM-12-12-centos test4]# 

7.3 硬链接理解

硬链接的本质就是把源文件的属性拷贝一份,然后把文件名改一下即可。也就是建立一个文件名,该文件名对目标文件inode做映射,直接指向目标文件inode即可。这里的硬链接数其实就是引用计数,inode相关的这个结构体中有对应的计数变量。另外inode中是不会包含文件名的,这个用例子来说:

[jyh@VM-12-12-centos link]$ ll
total 4
-rw-rw-r-- 1 jyh jyh 32 Apr  2 16:52 text.txt
[jyh@VM-12-12-centos link]$ ln text.txt hard-link
[jyh@VM-12-12-centos link]$ ll
total 8
-rw-rw-r-- 2 jyh jyh 32 Apr  2 16:52 hard-link
-rw-rw-r-- 2 jyh jyh 32 Apr  2 16:52 text.txt
[jyh@VM-12-12-centos link]$ cat hard-link 
1111111
1111111
1111111
2222222
[jyh@VM-12-12-centos link]$ cat text.txt 
1111111
1111111
1111111
2222222
[jyh@VM-12-12-centos link]$ 

硬链接的作用

[jyh@VM-12-12-centos link]$ mkdir directory
[jyh@VM-12-12-centos link]$ ll
total 8
drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 directory
-rw-rw-r-- 1 jyh jyh   32 Apr  2 16:52 text.txt
[jyh@VM-12-12-centos link]$ 

为什么目录的硬链接数默认是2呢?为什么普通文件的硬链接数默认是1呢?

[jyh@VM-12-12-centos directory]$ ls -al
total 8
drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 . //当前目录 
drwxrwxr-x 3 jyh jyh 4096 Apr  2 18:56 .. //上级目录
[jyh@VM-12-12-centos link]$ ll
total 8
drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 directory
-rw-rw-r-- 1 jyh jyh   32 Apr  2 16:52 text.txt
[jyh@VM-12-12-centos link]$ ls -il
total 8
1052356 drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 directory
1052355 -rw-rw-r-- 1 jyh jyh   32 Apr  2 16:52 text.txt
[jyh@VM-12-12-centos link]$ cd directory/
[jyh@VM-12-12-centos directory]$ ls -ail
total 8
1052356 drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 .
1052354 drwxrwxr-x 3 jyh jyh 4096 Apr  2 18:56 ..
[jyh@VM-12-12-centos directory]$ 
[jyh@VM-12-12-centos directory]$ ls -di /home/jyh/linux_-stu/study14/link/
1052354 /home/jyh/linux_-stu/study14/link/
[jyh@VM-12-12-centos directory]$ 
//发现:directory和directory/.目录的inode是一样的,所以默认为2的原因也就是因为有当前目录
//directory/..和link目录的inode是一样的

所以这里理解为什么执行文件时是这样的:./a.out:原因很简单,就是通用.当前目录来找到对应的inode,进而找到对应的二进制文件,进而拿到数据load到内存,进而运行起来。这里我们观察到当前目录中有.和…两个目录,并且Linux的文件是一个多叉树结构,此时可以联想到文件结构是:

微信图片_20230524022134.png注意:不能给目录建立硬链接,现象:

[jyh@VM-12-12-centos link]$ ll
total 4
drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 directory
[jyh@VM-12-12-centos link]$ ln directory/ hard-link
ln: ‘directory/’: hard link not allowed for directory
[jyh@VM-12-12-centos link]$ 

为什么呢?这里通过树状结构来理解

微信图片_20230524022234.png

上图是给tmp1建立硬链接放在d1目录下,此时就破环了树状结构,导致了环路路径问题

8. 动静态库

8.1 初识

这里Linux中也有库,在 /usr/lib64目录下,就有动静态库:

[root@VM-12-12-centos lib64]# ls /usr/lib64/libc*
/usr/lib64/libc-2.17.so                              /usr/lib64/libcmdif.a            /usr/lib64/libcroco-0.6.so.3        /usr/lib64/libc.so
/usr/lib64/libc.a                                    /usr/lib64/libc_nonshared.a      /usr/lib64/libcroco-0.6.so.3.0.1    /usr/lib64/libc.so.6
/usr/lib64/libcairo-script-interpreter.so.2          /usr/lib64/libcom_err.so         /usr/lib64/libcrypt-2.17.so         /usr/lib64/libc_stubs.a
/usr/lib64/libcairo-script-interpreter.so.2.11512.0  /usr/lib64/libcom_err.so.2       /usr/lib64/libcrypt.a               /usr/lib64/libcupscgi.so.1
/usr/lib64/libcairo.so.2                             /usr/lib64/libcom_err.so.2.1     /usr/lib64/libcrypto.so             /usr/lib64/libcupsimage.so.2
/usr/lib64/libcairo.so.2.11512.0                     /usr/lib64/libconfig.so.9        /usr/lib64/libcrypto.so.10          /usr/lib64/libcupsmime.so.1
/usr/lib64/libcap-ng.so.0                            /usr/lib64/libconfig++.so.9      /usr/lib64/libcrypto.so.1.0.2k      /usr/lib64/libcupsppdc.so.1
/usr/lib64/libcap-ng.so.0.0.0                        /usr/lib64/libconfig.so.9.1.3    /usr/lib64/libcryptsetup.so.12      /usr/lib64/libcups.so.2
/usr/lib64/libcap.so.2                               /usr/lib64/libconfig++.so.9.1.3  /usr/lib64/libcryptsetup.so.12.3.0  /usr/lib64/libcurl.so.4
/usr/lib64/libcap.so.2.22                            /usr/lib64/libcpupower.so.0      /usr/lib64/libcryptsetup.so.4       /usr/lib64/libcurl.so.4.3.0
/usr/lib64/libcidn-2.17.so                           /usr/lib64/libcpupower.so.0.0.0  /usr/lib64/libcryptsetup.so.4.7.0
/usr/lib64/libcidn.so                                /usr/lib64/libcrack.so.2         /usr/lib64/libcrypt.so
/usr/lib64/libcidn.so.1                              /usr/lib64/libcrack.so.2.9.0     /usr/lib64/libcrypt.so.1
[root@VM-12-12-centos lib64]# 

静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。

动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

库名:开头(lib)+命名+后缀(.so或者.a);比如上面的/usr/lib64/libc.so.6,其中这个库的命名是c,以lib为前提,.so.6为后缀,这里的6是版本编号。


上述描述的是库,下面我们看看头文件,其中头文件是包含在/usr/include这个目录下的:

[root@VM-12-12-centos ~]# ls /usr/include/
Display all 207 possibilities? (y or n)
aio.h               elf.h               gnu/                libio.h             netash/             pcre_stringpiece.h  signal.h            ucontext.h
aliases.h           elfutils/           gnu-versions.h      libiptc/            netatalk/           poll.h              sound/              ucp/
alloca.h            endian.h            grp.h               libipulog/          netax25/            printf.h            spawn.h             ucs/
a.out.h             envz.h              gshadow.h           libmnl/             netdb.h             profile.h           stab.h              uct/
argp.h              err.h               gssapi/             libnl3/             neteconet/          protocols/          stdc-predef.h       ulimit.h
argz.h              errno.h             gssapi.h            libudev.h           netinet/            pthread.h           stdint.h            unistd.h
ar.h                error.h             gssrpc/             limits.h            netipx/             pty.h               stdio_ext.h         ustat.h
arpa/               et/                 iconv.h             link.h              netiucv/            pwd.h               stdio.h             utime.h
asm/                execinfo.h          ieee754.h           linux/              netpacket/          python2.7/          stdlib.h            utmp.h
asm-generic/        fcntl.h             ifaddrs.h           locale.h            netrom/             python3.6m/         string.h            utmpx.h
assert.h            features.h          inttypes.h          lzma/               netrose/            rdma/               strings.h           valgrind/
bits/               fenv.h              ip6tables.h         lzma.h              nfs/                re_comp.h           sys/                values.h
byteswap.h          FlexLexer.h         iptables/           malloc.h            nlist.h             regex.h             syscall.h           verto.h
c++/                fmtmsg.h            iptables.h          math.h              nl_types.h          regexp.h            sysexits.h          verto-module.h
com_err.h           fnmatch.h           kadm5/              mcheck.h            nss.h               resolv.h            syslog.h            video/
complex.h           fpu_control.h       kdb.h               mellanox/           numacompat1.h       rpc/                systemd/            wait.h
cpio.h              fstab.h             keyutils.h          memory.h            numa.h              rpcsvc/             tar.h               wchar.h
cpufreq.h           fts.h               krad.h              mft/                numaif.h            sched.h             termio.h            wctype.h
crypt.h             ftw.h               krb5/               misc/               obstack.h           scsi/               termios.h           wordexp.h
ctype.h             _G_config.h         krb5.h              mntent.h            openssl/            search.h            tgmath.h            xen/
db_185.h            gconv.h             langinfo.h          monetary.h          paths.h             selinux/            thread_db.h         xlocale.h
db.h                gelf.h              lastlog.h           mqueue.h            pcrecpparg.h        semaphore.h         time.h              xtables.h
dirent.h            getopt.h            libdb/              mstflint/           pcrecpp.h           sepol/              ttyent.h            xtables-version.h
dlfcn.h             gio-unix-2.0/       libelf.h            mtcr_ul/            pcre.h              setjmp.h            uapi/               zconf.h
drm/                glib-2.0/           libgen.h            mtd/                pcreposix.h         sgtty.h             uchar.h             zlib.h
dwarf.h             glob.h              libintl.h           net/                pcre_scanner.h      shadow.h            ucm/          

所以这里的库到底是什么呢?头文件又是什么呢?其实我们随便打开一个库文件,我们发现它是个二进制文件,头文件我们打开发现都是函数的声明,那么我们可以想到,我们包含头文件,头文件只有声明我们怎么用呢,说明库中对应的全是函数功能实现,只不过是打包成了二进制文件,打包二进制这里用到了链接相关的知识,后面继续深入。初始我们建立的共同的想法,那么下面谈一谈一些尝试:我们在安装visual studio 2019等编译器软件的时候,其实就是安装头文件和库文件。另外还有使用编译器的时候,编译器会有自动提醒头文件或者函数等功能,这些都是通过遍历头文件和库文件来匹配完成检索的,还有语法报错功能,这也是编译器来进行检索功能。这就达成 了初始目的,下面我们再来谈一谈为什么的问题。


8.2 为什么要有

首先这些头文件和对应的库文件一安装环境就有了,里面有很多对应的轮子,可以拿来用,肯定是用别人的香啊,我们直接拿来用,这样就提高了开发效率。


8.3 静态库使用

场景:假如说现在有个日本人,这个日本人呢没办法设计一个加减方法,此时它就在网上找别人的库中,然后拉取到本地上来自己使用,此时我们是一个设计者,那么我们怎么来让这个日本人用到呢?

[jyh@VM-12-12-centos include]$ ls
add.c  add.h  sub.c  sub.h
[jyh@VM-12-12-centos include]$ cat add.h add.c
#pragma once
int add(int x, int y);
#include "add.h"
int add(int x, int y)
{
  return x + y;
}
[jyh@VM-12-12-centos include]$ cat sub.h sub.c
#pragma once
int sub(int x, int y);
#include "sub.h"
int sub(int x, int y)
{
  return x - y;
}
[jyh@VM-12-12-centos include]$ 

第一种方式,可以直接把代码发布在github上,这样这个日本人只需要调用对应的函数即可


上面这种方式可以,但是这里我们想要实现和声明分开,并且不想让这个日本人直接能看到源代码,这怎么办呢?前面我们观察东静态库全是二进制文件,那么怎么把一个.c的c语言文件变成一个二进制文件呢?之前有详细谈到程序编译的过程,程序编译有四个阶段:预处理、编译、汇编、链接,其中形成二进制文件就需要对文件完成汇编,对应的指令就是gcc -c 源文件 -o 重命名。

[jyh@VM-12-12-centos include]$ ls
add.c  add.h  sub.c  sub.h
[jyh@VM-12-12-centos include]$ gcc -c add.c -o add.o
[jyh@VM-12-12-centos include]$ gcc -c sub.c -o sub.o
[jyh@VM-12-12-centos include]$ ls
add.c  add.h  add.o  sub.c  sub.h  sub.o
[jyh@VM-12-12-centos include]$ mkdir lib
[jyh@VM-12-12-centos include]$ mkdir include
[jyh@VM-12-12-centos include]$ ls
add.c  add.h  add.o  include  lib  sub.c  sub.h  sub.o
[jyh@VM-12-12-centos include]$ mv add.o sub.o lib/
[jyh@VM-12-12-centos include]$ mv add.h sub.h include/
[jyh@VM-12-12-centos include]$ ls
add.c  include  lib  sub.c
[jyh@VM-12-12-centos include]$ tree
.
|-- add.c
|-- include
|   |-- add.h
|   `-- sub.h
|-- lib
|   |-- add.o
|   `-- sub.o
`-- sub.c
2 directories, 6 files
[jyh@VM-12-12-centos include]$ 

上述这样的方式也有点不行,原因就是当有很多源文件的时候那么就得形成多少个.o文件,这样就很烦,再通过对lib和include这两个目录进行打包上传到github上就可以供这个日本人使用,但是这里的缺陷就得改进,如何改进呢?可不可以直接把多个.o文件整体打包为一个库文件,是可以的,下面打包成静态库说明:(命令:ar -rc 库文件名 被打包的.o文件列表)

[jyh@VM-12-12-centos include]$ ls
add.c  include  lib  sub.c
[jyh@VM-12-12-centos include]$ tree
.
|-- add.c
|-- include
|   |-- add.h
|   `-- sub.h
|-- lib
|   |-- add.o
|   `-- sub.o
`-- sub.c
2 directories, 6 files
[jyh@VM-12-12-centos include]$ cd lib/
[jyh@VM-12-12-centos lib]$ ls
add.o  sub.o
[jyh@VM-12-12-centos lib]$ ar -rc libmath.a add.o sub.o //生成静态库
[jyh@VM-12-12-centos lib]$ ls
add.o  libmath.a  sub.o
[jyh@VM-12-12-centos lib]$ rm add.o sub.o 
[jyh@VM-12-12-centos lib]$ ls
libmath.a
[jyh@VM-12-12-centos lib]$ 

上面我们说了如何做的问题,下面来模仿这个日本人来完成怎么用的问题(直接用本地目录进行使用,并没有对其github拉去解包使用,这里只是演示本地,至于怎么拉取库解压库这里不做操作)

[jyh@VM-12-12-centos japanese]$ ll
total 8
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:30 include
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:27 lib
[jyh@VM-12-12-centos japanese]$ cp -r include/add.h include/sub.h .
[jyh@VM-12-12-centos japanese]$ ll
total 16
-rw-rw-r-- 1 jyh jyh   38 Apr  3 17:30 add.h
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:30 include
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:27 lib
-rw-rw-r-- 1 jyh jyh   37 Apr  3 17:30 sub.h
[jyh@VM-12-12-centos japanese]$ cp -r lib/libmath.a .
[jyh@VM-12-12-centos japanese]$ ll
total 20
-rw-rw-r-- 1 jyh jyh   38 Apr  3 17:30 add.h
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:30 include
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:27 lib
-rw-rw-r-- 1 jyh jyh 2688 Apr  3 17:30 libmath.a
-rw-rw-r-- 1 jyh jyh   37 Apr  3 17:30 sub.h
[jyh@VM-12-12-centos japanese]$ ls
add.h  include  lib  libmath.a  main.c  sub.h
[jyh@VM-12-12-centos japanese]$ rm -rf include/ lib/
[jyh@VM-12-12-centos japanese]$ ll
total 16
-rw-rw-r-- 1 jyh jyh   38 Apr  3 17:30 add.h
-rw-rw-r-- 1 jyh jyh 2688 Apr  3 17:30 libmath.a
-rw-rw-r-- 1 jyh jyh  209 Apr  3 17:32 main.c
-rw-rw-r-- 1 jyh jyh   37 Apr  3 17:30 sub.h
[jyh@VM-12-12-centos japanese]$ vim main.c
[jyh@VM-12-12-centos japanese]$ ls
add.h  include  lib  libmath.a  main.c  sub.h
[jyh@VM-12-12-centos japanese]$ gcc main.c
/tmp/ccZHGHDN.o: In function `main':
main.c:(.text+0x21): undefined reference to `add'
main.c:(.text+0x33): undefined reference to `sub'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ cat main.c 
#include "add.h"
#include "sub.h"
#include "stdio.h"
int main()
{
  int x = 10;
  int y = 20;
  int addNum = add(x,y);
  int subNum = sub(x,y);
  printf("addNum=%d subNum=%d\n", addNum, subNum);
  return 0;
}
[jyh@VM-12-12-centos japanese]$ 

上面不是自己写的main.c不是包含了"add.h"和"sub.h"头文件吗,为什么函数名没有定义呢?说明这里没有办法直接使用libmath.a这个库文件,那么为什么不能直接使用呢?编译器找不到库,为什么呢?原因是因为libmath.a这个库是第三方库,编译器并不认识,那么怎么才能使得编译器认识呢?带上库名给编译器认识(gcc 源文件 -L路径 -l库名)(所谓的第三方库就是除了第一方和第二方其他都是的,第一方库就是语言库,第二方就是操作系统库)

[jyh@VM-12-12-centos japanese]$ clear
[jyh@VM-12-12-centos japanese]$ ls
add.h  libmath.a  main.c  sub.h
[jyh@VM-12-12-centos japanese]$ gcc -o exe main.c 
/tmp/ccv7jpgR.o: In function `main':
main.c:(.text+0x21): undefined reference to `add'
main.c:(.text+0x33): undefined reference to `sub'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ gcc -o exe main.c -L. -lmath //-L:指定文件路径,-L.:当前路径下查找库 -l:指定库名(真正的库名是去掉前缀lib和后缀.so或者.的)
[jyh@VM-12-12-centos japanese]$ ll
total 28
-rw-rw-r-- 1 jyh jyh   38 Apr  3 17:30 add.h
-rwxrwxr-x 1 jyh jyh 8472 Apr  3 17:42 exe
-rw-rw-r-- 1 jyh jyh 2688 Apr  3 17:30 libmath.a
-rw-rw-r-- 1 jyh jyh  209 Apr  3 17:32 main.c
-rw-rw-r-- 1 jyh jyh   37 Apr  3 17:30 sub.h
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum=30 subNum=-10
[jyh@VM-12-12-centos japanese]$ 

这里也可以把库文件拷贝到系统中,这样就需要根据路径搜索了,不用gcc命令中带上-L选项了,也不用指定头文件路径了,这里把库和头文件拷贝到系统默认路径下,就是在Linux下安装库(这里不建议把库和头文件放进系统中):

[jyh@VM-12-12-centos japanese]$ ll
total 12
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:10 include
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:20 lib
-rw-rw-r-- 1 jyh jyh  209 Apr  3 17:32 main.c
[jyh@VM-12-12-centos japanese]$ cp -rf include/* /usr/include/
cp: cannot create regular file ‘/usr/include/add.h’: Permission denied
cp: cannot create regular file ‘/usr/include/sub.h’: Permission denied
[jyh@VM-12-12-centos japanese]$ sudo cp -rf include/* /usr/include/
[sudo] password for jyh: 
[jyh@VM-12-12-centos japanese]$ ls /usr/include/add.h
/usr/include/add.h
[jyh@VM-12-12-centos japanese]$ ls /usr/include/sub.h
/usr/include/sub.h
[jyh@VM-12-12-centos japanese]$ cp -rf lib/* /usr/lib64
cp: cannot create regular file ‘/usr/lib64/libmath.a’: Permission denied
[jyh@VM-12-12-centos japanese]$ sudo cp -rf lib/* /usr/lib64
[jyh@VM-12-12-centos japanese]$ ls /usr/lib64/libmath.a
/usr/lib64/libmath.a
[jyh@VM-12-12-centos japanese]$ gcc -o exe main.c
/tmp/cc30sUX9.o: In function `main':
main.c:(.text+0x21): undefined reference to `add'
main.c:(.text+0x33): undefined reference to `sub'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ 

这里还是显示错误:add和sub函数未定义。愿意是这里是第三方库,所以这里要指明库

[jyh@VM-12-12-centos japanese]$ gcc -o exe main.c
/tmp/cc30sUX9.o: In function `main':
main.c:(.text+0x21): undefined reference to `add'
main.c:(.text+0x33): undefined reference to `sub'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ gcc -o exe main.c -lmath
[jyh@VM-12-12-centos japanese]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8472 Apr  3 18:05 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:10 include
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:20 lib
-rw-rw-r-- 1 jyh jyh  209 Apr  3 17:32 main.c
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum=30 subNum=-10
[jyh@VM-12-12-centos japanese]$ 

8.4 动态库使用

.o文件生成必须使用:gcc/g++ -fPIC -c 源文件列表(其中fPIC:位置无关码(position independent code)

生成动态库:gcc/g++ -shared -o 库名 .o文件列表

[jyh@VM-12-12-centos study15]$ ll
total 16
-rw-rw-r-- 1 jyh jyh 64 Apr  4 09:33 myadd.c
-rw-rw-r-- 1 jyh jyh 40 Apr  4 09:33 myadd.h
-rw-rw-r-- 1 jyh jyh 63 Apr  4 09:34 mysub.c
-rw-rw-r-- 1 jyh jyh 39 Apr  4 09:34 mysub.h
[jyh@VM-12-12-centos study15]$ gcc -fPIC -c myadd.c
[jyh@VM-12-12-centos study15]$ gcc -fPIC -c mysub.c
[jyh@VM-12-12-centos study15]$ ll
total 24
-rw-rw-r-- 1 jyh jyh   64 Apr  4 09:33 myadd.c
-rw-rw-r-- 1 jyh jyh   40 Apr  4 09:33 myadd.h
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 myadd.o
-rw-rw-r-- 1 jyh jyh   63 Apr  4 09:34 mysub.c
-rw-rw-r-- 1 jyh jyh   39 Apr  4 09:34 mysub.h
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 mysub.o
[jyh@VM-12-12-centos study15]$ gcc -shared -o libmymath.so *.o
[jyh@VM-12-12-centos study15]$ ll
total 32
-rwxrwxr-x 1 jyh jyh 7944 Apr  4 09:34 libmymath.so
-rw-rw-r-- 1 jyh jyh   64 Apr  4 09:33 myadd.c
-rw-rw-r-- 1 jyh jyh   40 Apr  4 09:33 myadd.h
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 myadd.o
-rw-rw-r-- 1 jyh jyh   63 Apr  4 09:34 mysub.c
-rw-rw-r-- 1 jyh jyh   39 Apr  4 09:34 mysub.h
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 mysub.o
[jyh@VM-12-12-centos study15]$ mkdir include
[jyh@VM-12-12-centos study15]$ mkdir lib
[jyh@VM-12-12-centos study15]$ mv *.h include/
[jyh@VM-12-12-centos study15]$ mv *.so lib
[jyh@VM-12-12-centos study15]$ ll
total 24
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:35 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:35 lib
-rw-rw-r-- 1 jyh jyh   64 Apr  4 09:33 myadd.c
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 myadd.o
-rw-rw-r-- 1 jyh jyh   63 Apr  4 09:34 mysub.c
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 mysub.o
[jyh@VM-12-12-centos study15]$ 

那么学会了打包制作操作,接下来模仿other用户来使用相关动态库:

[jyh@VM-12-12-centos japanese]$ ll //github上拿到了包并进行解压后
total 8
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
[jyh@VM-12-12-centos japanese]$ cp -f include/myadd.h include/mysub.h .
[jyh@VM-12-12-centos japanese]$ cp -f lib/libmymath.so .
[jyh@VM-12-12-centos japanese]$ ll
total 24
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rwxrwxr-x 1 jyh jyh 7944 Apr  4 09:41 libmymath.so
-rw-rw-r-- 1 jyh jyh   40 Apr  4 09:40 myadd.h
-rw-rw-r-- 1 jyh jyh   39 Apr  4 09:40 mysub.h
[jyh@VM-12-12-centos japanese]$ touch main.c
[jyh@VM-12-12-centos japanese]$ ls
include  lib  libmymath.so  main.c  myadd.h  mysub.h
[jyh@VM-12-12-centos japanese]$ vim main.c 
[jyh@VM-12-12-centos japanese]$ cat main.c //手写测试用例
#include "stdio.h"
#include "myadd.h"
#include "mysub.h"
int main()
{
  int x = 20;
  int y = 10;
  int addNum = myadd(x, y);
  int subNum = mysub(x, y);
  printf("addNum = %d subNum = %d\n", addNum, subNum);
  return 0;
}
[jyh@VM-12-12-centos japanese]$ gcc main.c -o exe
/tmp/cc7mIEOJ.o: In function `main':
main.c:(.text+0x21): undefined reference to `myadd'
main.c:(.text+0x33): undefined reference to `mysub'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ gcc main.c -o exe -L. -lmymath //-L:
[jyh@VM-12-12-centos japanese]$ ll
total 40
-rwxrwxr-x 1 jyh jyh 8432 Apr  4 09:44 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rwxrwxr-x 1 jyh jyh 7944 Apr  4 09:41 libmymath.so
-rw-rw-r-- 1 jyh jyh  223 Apr  4 09:42 main.c
-rw-rw-r-- 1 jyh jyh   40 Apr  4 09:40 myadd.h
-rw-rw-r-- 1 jyh jyh   39 Apr  4 09:40 mysub.h
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum = 30 subNum = 10
[jyh@VM-12-12-centos japanese]$ 

但是下面方式导致了错误

[jyh@VM-12-12-centos japanese]$ ll
total 12
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rw-rw-r-- 1 jyh jyh  223 Apr  4 09:42 main.c
[jyh@VM-12-12-centos japanese]$ cat main.c 
#include "stdio.h"
#include "myadd.h"
#include "mysub.h"
int main()
{
  int x = 20;
  int y = 10;
  int addNum = myadd(x, y);
  int subNum = mysub(x, y);
  printf("addNum = %d subNum = %d\n", addNum, subNum);
  return 0;
}
[jyh@VM-12-12-centos japanese]$ gcc main.c
main.c:2:19: fatal error: myadd.h: No such file or directory
 #include "myadd.h"
                   ^
compilation terminated.
[jyh@VM-12-12-centos japanese]$ gcc -I include -L lib/ -lmymath -o exe
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ gcc main.c -I include -L lib/ -lmymath -o exe
[jyh@VM-12-12-centos japanese]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8432 Apr  4 09:49 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rw-rw-r-- 1 jyh jyh  223 Apr  4 09:42 main.c
[jyh@VM-12-12-centos japanese]$ ./exe 
./exe: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
[jyh@VM-12-12-centos japanese]$ 

这里不是告诉了库文件路径和头文件路径,也告诉了这个第三方库名吗?为什么不能编译通过呢?这里还导致找不到共享库呢?因为这里只是告诉了编译器gcc,并不是告诉系统,并不是在系统的默认路径下所以就找不到了,但是这里静态库为什么不需要放到系统默认路径就可以找到呢?因为静态库是将用户的二进制代码直接拷贝到目标可执行程序中,所以可以直接跑起来,可执行程序中就有相关的.o的二进制代码,但是动态库并不是的,可执行程序中没有相关.o文件的二进制代码,所以这里需要的操作有三种:(这里使用ldd命令:ldd 可执行文件;作用查找对应的依赖第三方库)


  1. 环境变量法(临时方案)
[jyh@VM-12-12-centos japanese]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8432 Apr  4 09:49 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rw-rw-r-- 1 jyh jyh  223 Apr  4 09:42 main.c
[jyh@VM-12-12-centos japanese]$ ldd exe 
  linux-vdso.so.1 =>  (0x00007ffda62ca000)
  libmymath.so => not found
  libc.so.6 => /lib64/libc.so.6 (0x00007f0a98578000)
  /lib64/ld-linux-x86-64.so.2 (0x00007f0a98946000) 
[jyh@VM-12-12-centos japanese]$ echo $LD_LIBRARY_PATH //查看链接库PATH
:/home/jyh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
[jyh@VM-12-12-centos japanese]$ pwd
/home/jyh/linux_-stu/study15/japanese
[jyh@VM-12-12-centos japanese]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/jyh/linux_-stu/study15/japanese/lib/ //导入路径
[jyh@VM-12-12-centos japanese]$ echo $LD_LIBRARY_PATH
:/home/jyh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/jyh/linux_-stu/study15/japanese/lib/
[jyh@VM-12-12-centos japanese]$ ldd exe 
  linux-vdso.so.1 =>  (0x00007ffcaeaea000)
  libmymath.so => /home/jyh/linux_-stu/study15/japanese/lib/libmymath.so (0x00007fdd58121000)
  libc.so.6 => /lib64/libc.so.6 (0x00007fdd57d53000)
  /lib64/ld-linux-x86-64.so.2 (0x00007fdd58323000)
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum = 30 subNum = 10
[jyh@VM-12-12-centos japanese]$ 
  1. 软链接方式(永久性的)
[jyh@VM-12-12-centos japanese]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8432 Apr  4 09:49 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rw-rw-r-- 1 jyh jyh  223 Apr  4 09:42 main.c
[jyh@VM-12-12-centos japanese]$ ldd exe 
  linux-vdso.so.1 =>  (0x00007ffd9cdad000)
  libmymath.so => not found
  libc.so.6 => /lib64/libc.so.6 (0x00007f61e8af8000)
  /lib64/ld-linux-x86-64.so.2 (0x00007f61e8ec6000)
[jyh@VM-12-12-centos japanese]$ sudo ln -s ./lib/libmymath.so /usr/lib64/libmymath.so //不是绝对路径不行
[jyh@VM-12-12-centos japanese]$ sudo ls /usr/lib64/libmath.so
ls: cannot access /usr/lib64/libmath.so: No such file or directory
[jyh@VM-12-12-centos japanese]$ sudo ls /usr/lib64/libmymath.so
/usr/lib64/libmymath.so
[jyh@VM-12-12-centos japanese]$ ldd exe 
  linux-vdso.so.1 =>  (0x00007ffddd50f000)
  libmymath.so => not found
  libc.so.6 => /lib64/libc.so.6 (0x00007f0d1f34b000)
  /lib64/ld-linux-x86-64.so.2 (0x00007f0d1f719000)
[jyh@VM-12-12-centos japanese]$ pwd 
/home/jyh/linux_-stu/study15/japanese
[jyh@VM-12-12-centos japanese]$ sudo ln -s /home/jyh/linux_-stu/study15/japanese/lib/libmymath.so /usr/lib64/libmymath.so
ln: failed to create symbolic link ‘/usr/lib64/libmymath.so’: File exists
[jyh@VM-12-12-centos japanese]$ rm /usr/lib64/libmymath.so 
rm: cannot remove ‘/usr/lib64/libmymath.so’: Permission denied
[jyh@VM-12-12-centos japanese]$ sudo rm /usr/lib64/libmymath.so 
[jyh@VM-12-12-centos japanese]$ sudo ln -s /home/jyh/linux_-stu/study15/japanese/lib/libmymath.so /usr/lib64/libmymath.so //必须使用绝对路径
[jyh@VM-12-12-centos japanese]$ sudo ls /usr/lib64/libmymath.so 
/usr/lib64/libmymath.so
[jyh@VM-12-12-centos japanese]$ ldd exe 
  linux-vdso.so.1 =>  (0x00007ffc4e5b5000)
  libmymath.so => /lib64/libmymath.so (0x00007f452382c000)
  libc.so.6 => /lib64/libc.so.6 (0x00007f452345e000)
  /lib64/ld-linux-x86-64.so.2 (0x00007f4523a2e000)
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum = 30 subNum = 10
[jyh@VM-12-12-centos japanese]$ 
  1. 配置文件方式(配置文件在/etc/ld.so.conf.d目录下)
[jyh@VM-12-12-centos japanese]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8432 Apr  4 18:43 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 18:43 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 18:43 lib
-rw-rw-r-- 1 jyh jyh  223 Apr  4 18:43 main.c
[jyh@VM-12-12-centos japanese]$ ldd exe 
  linux-vdso.so.1 =>  (0x00007fffbf799000)
  libmymath.so => not found
  libc.so.6 => /lib64/libc.so.6 (0x00007fe05040a000)
  /lib64/ld-linux-x86-64.so.2 (0x00007fe0507d8000)
[jyh@VM-12-12-centos japanese]$ ./exe 
./exe: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
[jyh@VM-12-12-centos japanese]$ ls /etc/ld.so.conf.d/
bind-export-x86_64.conf  dyninst-x86_64.conf  kernel-3.10.0-1160.71.1.el7.x86_64.conf  mariadb-x86_64.conf
[jyh@VM-12-12-centos japanese]$ vim /etc/ld.so.conf.d/dyninst-x86_64.conf 
[jyh@VM-12-12-centos japanese]$ sudo touch /etc/ld.so.conf.d/mymath.conf//必须提权,因为是系统配置文件必须root身份
[jyh@VM-12-12-centos japanese]$ ls /etc/ld.so.conf.d/
bind-export-x86_64.conf  dyninst-x86_64.conf  kernel-3.10.0-1160.71.1.el7.x86_64.conf  mariadb-x86_64.conf  mymath.conf
[jyh@VM-12-12-centos japanese]$ pwd lib/
/home/jyh/linux_-stu/study16/japanese
[jyh@VM-12-12-centos japanese]$ cd lib/
[jyh@VM-12-12-centos lib]$ ls
libmymath.so
[jyh@VM-12-12-centos lib]$ pwd
/home/jyh/linux_-stu/study16/japanese/lib
[jyh@VM-12-12-centos lib]$ cd ..
[jyh@VM-12-12-centos japanese]$ sudo vim /etc/ld.so.conf.d/mymath.conf//必须提权,因为是系统配置文件必须root身份
[jyh@VM-12-12-centos japanese]$ cat /etc/ld.so.conf.d/mymath.conf 
/home/jyh/linux_-stu/study16/japanese/lib
[jyh@VM-12-12-centos japanese]$ ldd exe 
  linux-vdso.so.1 =>  (0x00007ffd29348000)
  libmymath.so => not found
  libc.so.6 => /lib64/libc.so.6 (0x00007fd8b62cd000)
  /lib64/ld-linux-x86-64.so.2 (0x00007fd8b669b000)
[jyh@VM-12-12-centos japanese]$ sudo ldconfig //ldconfig更新配置文件
[jyh@VM-12-12-centos japanese]$ ldd exe 
  linux-vdso.so.1 =>  (0x00007ffc9e5e8000)
  libmymath.so => /home/jyh/linux_-stu/study16/japanese/lib/libmymath.so (0x00007f25b0edf000)
  libc.so.6 => /lib64/libc.so.6 (0x00007f25b0b11000)
  /lib64/ld-linux-x86-64.so.2 (0x00007f25b10e1000)
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum = 30 subNum = 10
[jyh@VM-12-12-centos japanese]$ 

8.5 动静态库加载问题

  • 静态库

上述说到了静态库问题,当程序形成可执行程序的时候,这个可执行程序中已经包含了静态库中被转化为二进制代码的函数功能实现,此时形成.exe文件后,把静态库删除了依旧可以运行正常,这相比动态库,静态库的可执行文件要大的多,因为动态库中被转化为二进制程序的函数功能实现并不是直接拷贝到.exe文件中的。所以总结出一个结论:静态库非常占用资源(磁盘资源等等资源)

[jyh@VM-12-12-centos staticIncludeTest]$ ll
total 28
-rw-rw-r-- 1 jyh jyh  202 Apr  4 19:45 main.c
-rw-rw-r-- 1 jyh jyh   77 Apr  4 19:47 makefile
-rw-rw-r-- 1 jyh jyh  680 Apr  4 19:43 test.c
-rw-rw-r-- 1 jyh jyh  352 Apr  4 19:45 test.h
[jyh@VM-12-12-centos staticIncludeTest]$ cat main.c 
#include "test.h"
int main()
{
  srand(time(NULL));
  Name name;
  giveStorage(&name);
  giveFiveNumber(&name);
  print(&name);
  printf("别点到的名字是:%s\n", getNumber(&name));
  return 0;
}
[jyh@VM-12-12-centos staticIncludeTest]$ cat makefile 
myexe:main.c test.c
  gcc -o $@ $^ -std=c99
.PHONY:clean
clean:
  rm -f myexe
[jyh@VM-12-12-centos staticIncludeTest]$ cat test.c
#include "test.h"
void giveStorage(Name* name)
{
  name->index = 0;
  name = (Name*)malloc(sizeof(Name));
  if(name == NULL) {perror("giveStorage():name malloc failed!\n"); exit(0);}
  for(int i = 0; i < 5; ++i)
  {
    name->s[i] = (char*)malloc(sizeof(char) * 10);
  }
}
void giveFiveNumber(Name* name)
{
  name->s[0] = "Johnson";
  name->s[1] = "Tom";
  name->s[2] = "Timy";
  name->s[3] = "Junisy";
  name->s[4] = "Kangkang";
}
void print(Name* name)
{
  printf("五个成员分别是:\n");
  for(int i = 0; i < 5; ++i)
  {
    printf("%s ", name->s[i]);
  }
  printf("\n");
}
char* getNumber(Name* name)
{
  name->index = rand() % 5;
  return name->s[name->index];
}
[jyh@VM-12-12-centos staticIncludeTest]$ cat test.h
//实现点名器
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include <string.h>
typedef struct Name
{
  char* s[5];
  int index;
}Name;
//申请内存
void giveStorage(Name* name);
//给定成员
void giveFiveNumber(Name* name);
//打印成员
void print(Name* name);
//随机点名
char* getNumber(Name* name);
[jyh@VM-12-12-centos staticIncludeTest]$ mkdir include
[jyh@VM-12-12-centos staticIncludeTest]$ mkdir lib
[jyh@VM-12-12-centos staticIncludeTest]$ mv test.h include
[jyh@VM-12-12-centos staticIncludeTest]$ mv test.o lib
[jyh@VM-12-12-centos staticIncludeTest]$ cd lib/
[jyh@VM-12-12-centos lib]$ ll
total 4
-rw-rw-r-- 1 jyh jyh 2696 Apr  4 19:49 test.o
[jyh@VM-12-12-centos lib]$ ar -rc libRandGetName.a test.o 
[jyh@VM-12-12-centos lib]$ ll
total 8
-rw-rw-r-- 1 jyh jyh 2888 Apr  4 19:55 libRandGetName.a
-rw-rw-r-- 1 jyh jyh 2696 Apr  4 19:49 test.o
[jyh@VM-12-12-centos lib]$ rm test.o
[jyh@VM-12-12-centos lib]$ cd ..
[jyh@VM-12-12-centos staticIncludeTest]$ rm makefile test.c
[jyh@VM-12-12-centos staticIncludeTest]$ ll
total 12
drwxrwxr-x 2 jyh jyh 4096 Apr  4 19:53 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 19:55 lib
-rw-rw-r-- 1 jyh jyh  202 Apr  4 19:45 main.c
[jyh@VM-12-12-centos staticIncludeTest]$ tree
.
|-- include
|   `-- test.h
|-- lib
|   `-- libRandGetName.a
`-- main.c
2 directories, 3 files
[jyh@VM-12-12-centos staticIncludeTest]$ 
[jyh@VM-12-12-centos staticIncludeTest]$ gcc -o exe main.c 
main.c:1:18: fatal error: test.h: No such file or directory
 #include "test.h"
                  ^
compilation terminated.
[jyh@VM-12-12-centos staticIncludeTest]$ gcc -o exe main.c -I include -L lib -lRandGetName
[jyh@VM-12-12-centos staticIncludeTest]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8920 Apr  4 19:57 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 19:53 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 19:55 lib
-rw-rw-r-- 1 jyh jyh  202 Apr  4 19:45 main.c
[jyh@VM-12-12-centos staticIncludeTest]$ ./exe 
五个成员分别是:
Johnson Tom Timy Junisy Kangkang 
别点到的名字是:Junisy
[jyh@VM-12-12-centos staticIncludeTest]$ ./exe 
五个成员分别是:
Johnson Tom Timy Junisy Kangkang 
别点到的名字是:Kangkang
[jyh@VM-12-12-centos staticIncludeTest]$ ./exe 
五个成员分别是:
Johnson Tom Timy Junisy Kangkang 
别点到的名字是:Timy
[jyh@VM-12-12-centos staticIncludeTest]$ rm lib/libRandGetName.a //删除静态库依旧可以执行./exe
[jyh@VM-12-12-centos staticIncludeTest]$ ll lib/
total 0
[jyh@VM-12-12-centos staticIncludeTest]$ ./exe 
五个成员分别是:
Johnson Tom Timy Junisy Kangkang 
别点到的名字是:Johnson
[jyh@VM-12-12-centos staticIncludeTest]$ ./exe 
五个成员分别是:
Johnson Tom Timy Junisy Kangkang 
别点到的名字是:Johnson
[jyh@VM-12-12-centos staticIncludeTest]$ 
  • 动态库
动态库


























目录
打赏
0
0
0
0
0
分享
相关文章
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
136 0
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
69 34
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
191 1
Linux C/C++之IO多路复用(aio)
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
(已成功解决)Linux环境报错—bash: wget: command not found;常见Linux发行版本,Linux中yum、rpm、apt-get、wget的区别;Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
786 68
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
|
3月前
|
Linux基础IO
Linux基础IO操作是系统管理和开发的基本技能。通过掌握文件描述符、重定向与管道、性能分析工具、文件系统操作以及网络IO命令等内容,可以更高效地进行系统操作和脚本编写。希望本文提供的知识和示例能帮助读者更深入地理解和运用Linux IO操作。
79 14
|
5月前
|
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
107 0
Linux C/C++之IO多路复用(poll,epoll)
Linux之缓冲区与C库IO函数简单模拟
通过上述编程实例,可以对Linux系统中缓冲区和C库IO函数如何提高文件读写效率有了一个基本的了解。开发者需要根据应用程序的具体需求来选择合适的IO策略。
55 0
Linux源码阅读笔记14-IO体系结构与访问设备
Linux源码阅读笔记14-IO体系结构与访问设备
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
99 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等