Linux基础IO

知识点回顾与引导

代码解释:w:以写的方式打开

1.如果不存在,就在当前路径下新建指定的文件

2.默认打开文件的时候,就会先把目标文件清空 

所以可以用来新建文件,清空文件

指令>与其类似,本质就是文件操作

a:追加写入的方式打开

不会清空文件

指令>>与其类似,本质就是文件操作

我们要进行文件操作,前提是我们的程序跑起来了,文件打开和关闭,是CPU在执行我们的代码 

打开文件本质是进程打开文件

文件没有打开的时候在磁盘,一个进程可以打开很多文件

很多情况下,OS内部,一定存在大量被打开的文件

文件=属性+内容

管理方式:先描述,再组织  类似PCB的结构体


理解文件

a.文件操作,本质:进程在操作文件

b.文件 -> 磁盘 -> 外设 -> 硬件 -> 向文件中写入,本质是向硬件中写入 -> 用户没有权利直接写入 -> OS是硬件的管理者 -> 通过OS写入 -> OS给我们提供系统调用

访问文件我们可以用系统调用,也可以用封装好的函数

fopen/fwrite/fread/fprintf/scanf/printf/cin/cout?->我们用的C/C++/...都是对系统调用接口的封装!

使用和认识系统调用的文件操作

打开文件系统接口函数

pathname:文件路径或者文件名(当前路径下)

flags:位图传递

32个比特位,用比特位进行标志位的传递

mode:文件权限

实际文件权限 mode&~umask

返回值:(int类型变量)fd

O_WRONLY:写方式打开

O_CREAT:不存在就当前路径创建

O_TRUNC:存在就清空

O_APPEND:存在就追加

系统中有文件掩码,根据就近原则这里使用代码里设置的文件掩码0

O_WRONLY 和 O_CREAT :宏 内部是一个传递位图标记位的函数

下面为大概模拟一下功能原理

#define ONE   1      // 1 0000 0001
#define TWO   (1<<1) // 2 0000 0010
#define THREE (1<<2) // 4 0000 0100
#define FOUR  (1<<3) // 8 0000 1000

void print(int flag)
{
    if(flag&ONE)
        printf("one\n"); //替换成其他功能
    if(flag&TWO)
        printf("two\n");
    if(flag&THREE)
        printf("three\n");
    if(flag&FOUR)
        printf("four\n");
}

int main()
{
    print(ONE);
    printf("\n");

    print(TWO);
    printf("\n");

    print(ONE|TWO);
    printf("\n");

    print(ONE|TWO|THREE);
    printf("\n");

    print(ONE|FOUR);
    printf("\n");

    print(ONE|TWO|THREE|FOUR);
    printf("\n");

    return 0;
}

关闭文件系统接口函数

向文件中写入系统接口函数

向文件中读出数据系统接口函数

int类型变量 fd由open函数返回

实验:

文件中已有字符串“hello Linux”,但位图没有清空和追加的位图传递。所以在开头写入。

fd是什么

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    int fda = open("loga.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    printf("fda: %d\n", fda);
    int fdb = open("logb.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    printf("fdb: %d\n", fdb);
    int fdc = open("logc.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    printf("fdc: %d\n", fdc);
    int fdd = open("logd.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    printf("fdd: %d\n", fdd);

    return 0;
}

C语言封装操作函数中的FILE是一个结构体,里面包含fd的数据

文件描述符fd的本质是:内核的进程:文件映射关系的数组的下标

系统一启动,默认打开三个文件,0:标准输入,1:标准输出,2:标准错误

我们调用C语言封装的函数底层就调用系统调用接口了

open在干什么呢

1.创建file

2.开辟文件缓冲区的空间,加载文件数据

3.查进程的文件描述符表

4.file地址填入对应的表下标中

5.返回下标

无论读写,都必须在合适的时候,让OS把文件的内容读到文件缓冲区中

Linux一切皆是文件,对于硬件的管理,也是当成文件进行管理

C语言封装操作函数中的FILE是一个结构体,里面包含fd的数据,直接打印出来

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
 printf("stdin->fd: %d\n", stdin->_fileno);
 printf("stdout->fd: %d\n", stdout->_fileno);
 printf("stderr->fd: %d\n", stderr->_fileno);


    FILE *fp = fopen("log.txt", "w");
    if(fp == NULL) return 1;
    printf("fd: %d\n", fp->_fileno); 

    FILE *fp1 = fopen("log1.txt", "w");
    if(fp == NULL) return 1;
    printf("fd: %d\n", fp1->_fileno); 

    FILE *fp2 = fopen("log2.txt", "w");
    if(fp == NULL) return 1;
    printf("fd: %d\n", fp2->_fileno); 

    fclose(fp);
    return 0;

}

所有的C语言上面的文件操作函数,本质底层都是对系统调用的封装

C语言为什么要这样做

既可以使用系统调用,也可以使用语言提供的文件方法

->系统不同,系统调用的接口可能不一样

->代码不具备跨平台性

->所以所有的语言都想要跨平台性

->所有的语言要对不同的平台的系统调用进行封装

->不同的语言封装的时候,文件接口就有差别了

stat系统接口

这三个函数是输出型系统接口函数,输出一个结构体

文件=内容+属性

这结构体是获取某个文件属性的结构体

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

const char *filename = "log.txt";
int main()
{
    struct stat st;
    int n = stat(filename, &st);
    if(n<0) return 1;

    printf("file size: %lu\n", st.st_size);

    int fd = open(filename, O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return 2;
    }
    printf("fd: %d\n", fd);

    char *file_buffer = (char*)malloc(st.st_size + 1);

    n = read(fd, file_buffer, st.st_size);
    if(n > 0)
    {
        file_buffer[n] = '\0';
        printf("%s", file_buffer);
    }

    free(file_buffer);


    close(fd);
    return 0;
}

文件描述符的分配规则

输出发现是 fd: 3
关闭 0 或者 2 ,在看
发现是结果是: fd: 0 或者 fd 2
文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符

重定向

那如果关闭1呢?看代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
 close(1);
 int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
 if(fd < 0){
 perror("open");
 return 1;
 }
 printf("fd: %d\n", fd);
 fflush(stdout);
 
 close(fd);
 exit(0);
}
此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd1。这种现象叫做输出重定向。
常见的重定向有:>, >>, <

printf/fprintf->stdout->struct FILE->stdout->fileno ==1

重定向的本质:是在内核中改变文件描述符表特定下标的内容,和上层无关

每用C语言打开一个文件,就会形成一个struct FILE结构体存储相关信息和语言缓冲区

int dup2(int oldfd,int newfd)

本质是文件描述符下标所对应内容的拷贝

oldfd拷贝给newfd

这里将下标1对应的显示器改为文件,也就向文件追加打印“hello world”

缓冲区的理解

 缓冲区的理解

调用系统调用是具有成本的,尽量要少调用,效率就高了

缓冲区刷新策略

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

const char *filename = "log.txt";

int main()
{
    // C
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    // system call
    const char *msg = "hello write\n";
    write(1, msg, strlen(msg));

    fork(); //???


    return 0;
}

向文件中写入,缓冲区满才刷新

这里创建了父子进程,C语言写入函数先写入用户缓冲区,系统接口写入函数直接写入文件内核缓存。最后父子进程结束,刷新两次用户缓冲区,将数据导入内核缓存两次从而写进文件

我们来看看FILE结构体,里面包含用户语言级缓冲区
/usr/include/libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
// 缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; // 封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

myshell功能的完善

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)
#define SkipSpace(cmd, pos) do{\
    while(1){\
        if(isspace(cmd[pos]))\
            pos++;\
        else break;\
    }\
}while(0)

// "ls -a -l -n > myfile.txt"
#define None_Redir 0
#define In_Redir   1
#define Out_Redir  2
#define App_Redir  3

int redir_type = None_Redir;
char *filename = NULL;

// 为了方便,我就直接定义了
char cwd[SIZE*2];
char *gArgv[NUM];
int lastcode = 0;

void Die()
{
    exit(1);
}

const char *GetHome()
{
    const char *home = getenv("HOME");
    if(home == NULL) return "/";
    return home;
}

const char *GetUserName()
{
    const char *name = getenv("USER");
    if(name == NULL) return "None";
    return name;
}
const char *GetHostName()
{
    const char *hostname = getenv("HOSTNAME");
    if(hostname == NULL) return "None";
    return hostname;
}
// 临时
const char *GetCwd()
{
    const char *cwd = getenv("PWD");
    if(cwd == NULL) return "None";
    return cwd;
}

// commandline : output
void MakeCommandLineAndPrint()
{
    char line[SIZE];
    const char *username = GetUserName();
    const char *hostname = GetHostName();
    const char *cwd = GetCwd();

    SkipPath(cwd);
    snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, strlen(cwd) == 1 ? "/" : cwd+1);
    printf("%s", line);
    fflush(stdout);
}

int GetUserCommand(char command[], size_t n)
{
    char *s = fgets(command, n, stdin);
    if(s == NULL) return -1;
    command[strlen(command)-1] = ZERO;
    return strlen(command); 
}


void SplitCommand(char command[], size_t n)
{
    (void)n;
    // "ls -a -l -n" -> "ls" "-a" "-l" "-n" 
    gArgv[0] = strtok(command, SEP);
    int index = 1;
    while((gArgv[index++] = strtok(NULL, SEP))); // done, 故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL,刚好让gArgv最后一个元素是NULL, 并且while判断结束
}

void ExecuteCommand()
{
    pid_t id = fork();
    if(id < 0) Die();
    else if(id == 0)
    {
        //重定向设置
        if(filename != NULL){
            if(redir_type == In_Redir)
            {
                int fd = open(filename, O_RDONLY);
                dup2(fd, 0);
            }
            else if(redir_type == Out_Redir)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                dup2(fd, 1);
            }
            else if(redir_type == App_Redir)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
                dup2(fd, 1);
            }
            else
            {}
        }

        // child
        execvp(gArgv[0], gArgv);
        exit(errno);
    }
    else
    {
        // fahter
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0)
        {
            lastcode = WEXITSTATUS(status);
            if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
        }
    }
}

void Cd()
{
    const char *path = gArgv[1];
    if(path == NULL) path = GetHome();
    // path 一定存在
    chdir(path);

    // 刷新环境变量
    char temp[SIZE*2];
    getcwd(temp, sizeof(temp));
    snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
    putenv(cwd); // OK
}

int CheckBuildin()
{
    int yes = 0;
    const char *enter_cmd = gArgv[0];
    if(strcmp(enter_cmd, "cd") == 0)
    {
        yes = 1;
        Cd();
    }
    else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
    {
        yes = 1;
        printf("%d\n", lastcode);
        lastcode = 0;
    }
    return yes;
}

void CheckRedir(char cmd[])
{
    // > >> <
    // "ls -a -l -n >  myfile.txt"
    int pos = 0;
    int end = strlen(cmd);

    while(pos < end)
    {
        if(cmd[pos] == '>')
        {
            if(cmd[pos+1] == '>')
            {
                cmd[pos++] = 0;
                pos++;
                redir_type = App_Redir;
                SkipSpace(cmd, pos);
                filename = cmd + pos;
            }
            else
            {
                cmd[pos++] = 0;
                redir_type = Out_Redir;
                SkipSpace(cmd, pos);
                filename = cmd + pos;
            }
        }
        else if(cmd[pos] == '<')
        {
            cmd[pos++] = 0;
            redir_type = In_Redir;
            SkipSpace(cmd, pos);
            filename = cmd + pos;
        }
        else
        {
            pos++;
        }
    }
}

int main()
{
    int quit = 0;
    while(!quit)
    {
        // 0. 重置
        redir_type = None_Redir;
        filename = NULL;
        // 1. 我们需要自己输出一个命令行
        MakeCommandLineAndPrint();

        // 2. 获取用户命令字符串
        char usercommand[SIZE];
        int n = GetUserCommand(usercommand, sizeof(usercommand));
        if(n <= 0) return 1;

        // 2.1 checkredir
        CheckRedir(usercommand);

        // 2.2 debug
//        printf("cmd: %s\n", usercommand);
//        printf("redir: %d\n", redir_type);
//        printf("filename: %s\n", filename);
//
        // 3. 命令行字符串分割. 
        SplitCommand(usercommand, sizeof(usercommand));

        // 4. 检测命令是否是内建命令
        n = CheckBuildin();
        if(n) continue;
        // 5. 执行命令
        ExecuteCommand();
    }
    return 0;
}

简单库的模拟与封装

mystdio.h

#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define LINE_SIZE 1024
#define FLUSH_NOW  1
#define FLUSH_LINE 2
#define FLUSH_FULL 4

struct _myFILE
{
    unsigned int flags;
    int fileno;
    // 缓冲区
    char cache[LINE_SIZE];
    int cap;
    int pos; // 下次写入的位置
};

typedef struct  _myFILE myFILE;
myFILE* my_fopen(const char *path, const char *flag);
void my_fflush(myFILE *fp);
ssize_t my_fwrite(myFILE *fp, const char *data, int len);
void my_fclose(myFILE *fp);

mystdio.c

#include "mystdio.h"


myFILE* my_fopen(const char *path, const char *flag)
{
    int flag1 = 0;
    int iscreate = 0;
    mode_t mode = 0666;
    if(strcmp(flag, "r") == 0)
    {
        flag1 = (O_RDONLY);
    }
    else if(strcmp(flag, "w") == 0)
    {
        flag1 = (O_WRONLY | O_CREAT | O_TRUNC);
        iscreate = 1;
    }
    else if(strcmp(flag, "a") == 0)
    {
        flag1 = (O_WRONLY | O_CREAT | O_APPEND);
        iscreate = 1;
    }
    else
    {}

    int fd = 0;
    if(iscreate)
        fd = open(path, flag1, mode);
    else
        fd = open(path, flag1);

    if(fd < 0) return NULL;

    myFILE *fp = (myFILE*)malloc(sizeof(myFILE));
    if(!fp) return NULL;

    fp->fileno = fd;
    fp->flags = FLUSH_LINE;

    fp->cap = LINE_SIZE;
    fp->pos = 0;

    return fp;
}

void my_fflush(myFILE *fp)
{
    write(fp->fileno, fp->cache, fp->pos);
    fp->pos = 0;
}

ssize_t my_fwrite(myFILE *fp, const char *data, int len)
{
    // 写入操作本质是拷贝, 如果条件允许,就刷新,否则不做刷新
    memcpy(fp->cache+fp->pos, data, len); //肯定要考虑越界, 自动扩容
    fp->pos += len;

    if((fp->flags&FLUSH_LINE) && fp->cache[fp->pos-1] == '\n')
    {
        my_fflush(fp);
    }

    return len;
}

void my_fclose(myFILE *fp)
{
    my_fflush(fp);
    close(fp->fileno);
    free(fp);
}

C语言为什么要在FILE中用户级缓冲区---为了减少底层调用系统调用的次数,让使用CIO函数

(printf,fprintf)效率更高

stderr(fd=2)是什么

stdout和stderr都连接显示器文件,是为了区分正确和错误的消息分成了两个结构体

正确消息是通过stdout打印显示器

错误消息是通过stderr打印显示器

我们可以重新定向,区分正确与错误消息的打印

./a.out > log.txt  这样默认重定向fd=1结构体的指向,指向log.txt文件

./a.out 1>all.log 2>err.log  这样可以重定向指向两个文件实现分类

./a.out 1>all.log 2>&1 正确和错误消息写在一个文件

printf();   cout<<; 都是向1打印

perror();  cerr<<; 都是向2打印

磁盘与文件

 文件=内容+属性 == 都是数据 == 二进制数据

如何找到这个文件 -> 要在磁盘中找到 -> 通过文件路径或文件名

没有打开的文件都在磁盘中存放

磁盘是一个机械设备,是一个外设。

盘片:可读可写可擦除,一盘两面都可以写,一面一个磁头

磁盘中盘片旋转是为了定位扇区

磁头左右摆动定位磁道(柱面)

磁盘读写的基本单位是扇区,512字节=4KB 

1片=n磁道

1磁道=m扇区

如何找到一个指定位置的扇区?

OS是如何通过文件地址调用磁盘找到文件的?

文件=很多个sector(扇区)的数组的下标

设一个盘面有1000扇区,10个磁道,每个磁道100扇区,index为下标

那么

index/1000=H; 磁头

index%1000=temp;[0,999]

temp/100=C; 磁道

temp%100=S; 扇区

一般而言,OS和磁盘交互的时候

基本单位:4KB=8个连续的扇区

所以文件=很多个块的组成

LBA:块中首扇区地址

对OS而言,我们读取数据可以以块为单位

0*8=0

1*8=8

2*8=16

只要我们有该文件所处块的下标,我们就能得出该块首元素扇区的下标,从而得到CHS

文件的分区管理

一个能管理好,那么所有的都能管理好

理解文件系统

文件=内容+属性(也是数据)

文件的磁盘存储,本质是:文件的内容+文件的属性数据

Linux文件系统特定:文件内容和文件属性分开存储

Block Groupext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相 同的结构组成。政府管理各区的例子
超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck       inode的总量, 未使用的blockinode的数量,一个blockinode的大小,最近一次挂载的时间,最近一次写入数据的 时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个 文件系统结构就被破坏了(不是每个分组都有)
GDT,Group Descriptor Table:块组描述符,描述块组属性信息
块位图(Block Bitmap)Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没 有被占用
inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。
inode节点表:存放文件属性 如 文件大小,所有者,最近修改时间等
数据区:存放文件内容
Linux中文件的属性是一个大小固定的结构体
一个正常文件,一个inode属性集合
struct inode
{
  int size;
  mode_t mode;
  int creater;
  int time;
  ......
  int datablocks[N];
}
大小128字节
inode内部不包含文件名,内核层面每一个文件都要有inode number,我们通过这个inode号标识一个文件
我们寻找文件的时候必须先得到文件的inode号
(inode编号是以分区为单位的,datablock也是如此)

int datablocks[N];

文件较大时,可通过拓展映射存储到磁盘

ext2->ext3->ext4

不同的拓展文件系统所能拓展的容量不一样

inode编号如何拿到

目录=文件属性+文件内容

目录的文件内容就是文件名和inode编号的映射关系

我们使用文件名的时候,就自动索引到文件名对应的inode编号

找到指定的文件->文件所在的目录->打开->根据文件名索引inode->目标文件的inode

目录也是文件,目录名索引它的inode

逆向的路径解析

/home/whb/111/code/lesson/dir

inode在哪一个分区

Linux会缓存路径结构

目录通常是谁提供的,进程早就提供了

内核文件系统提前写入并组织好,然后提供

软硬链接

a.见一见

软链接

硬链接

b.特征

1.软链接是一个独立的文件,因为有独立的inode number

软链接的内容->目标文件所对应的路径字符串,类似于windows的快捷方式

rm或unlink删除源文件后就会出现报错

2.硬链接不是一个独立的文件,因为没有独立的inode number,用的是目标文件inode number

3.属性中有一列硬链接数

硬链接就是一个文件名和inode的映射关系,建立硬链接就是在指定目录下添加一个新的文件名和inode number的映射关系,删除一个,文件还存在

2:这是引用系数,文件磁盘级引用计数:有多少个文件名字符串通过inode number指向我

所以定位一个文件只有两种方式

1.通过文件路径(最终还是要inode number)(软链接)

2.直接找到目标文件的inode (硬链接)

c.软硬链接有什么用

结论:


Linux系统中,不允许给目录建立硬链接,避免形成路径环绕

例如一个硬链接给/目录做链接,ls或者找文件就会形成死循环

目录中的. 和 .. 是硬链接

文件名是固定的,所有的系统指令在设定时的时候几乎都能知道. 和 .. 干什么

结论:1.构建Linux的路径结构,让我们可以使用 .和..来进行路径定位

           2.一般用硬链接来做文件备份

总结:

文本写入,二进制写入都是语言层的概念,我们写入文件都需要先转换为字符再写入

int a=123456;四字节  123456->"123456"->文件(文本写入) 6字节

这个工作由C语言做(用Ascall码表),系统接口函数不做

动态库和静态库

静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking
动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间

这是一个可执行程序链接的库

C语言动态库

C++标准库

Linux .so(动态库) .a(静态库)

静态库

知识引入

头文件是一个手册,提供函数的声明,告诉用户怎么用,.o提供实现,我们只需补上一个main,调用头文件提供的方法,然后和.o进行连接,就能实现可执行

将.o文件进行打包就是静态库

 我们将静态库和头文件封装安装到系统里面然后调用

调用需要去头截尾

[root@localhost linux]# ls
add.c add.h main.c sub.c sub.h
[root@localhost linux]# gcc -c add.c -o add.o
[root@localhost linux]# gcc -c sub.c -o sub.o
生成静态库
[root@localhost linux]# ar -rc libmymath.a add.o sub.o
ar gnu 归档工具, rc 表示 (replace and create)
查看静态库中的目录列表
[root@localhost linux]# ar -tv libmymath.a
rw-r--r-- 0/0 1240 Sep 15 16:53 2017 add.o
rw-r--r-- 0/0 1240 Sep 15 16:53 2017 sub.o
t: 列出静态库中的文件
v:verbose 详细信息
[root@localhost linux]# gcc main.c -L. -lmymath
-L 指定库路径
-l 指定库名
测试目标文件生成后,静态库删掉,程序照样可以运行

如果静态库和头文件封装后没有安装在系统目录中,需要这样调用

需要指定头文件路径和库路径+具体的库名称缩写


头文件路径可以不写,但必须包含的时候包含头文件路径

动态库

shared: 表示生成共享库格式
fPIC:产生位置无关码(position independent code)
库名规则:libxxx.so
示例: [root@localhost linux]# gcc -fPIC -c sub.c add.c [root@localhost linux]# gcc -shared -o libmymath.so *.o
[root@localhost linux]# ls add.c add.h add.o libmymath.so main.c sub.c sub.h sub.o

使用 

gcc在不使用static选项的时候,默认使用动态库

这里正常生成,但执行调用动态库时,出现执行失败

原因:没有找到动态库

方法:

a.

b.

c.

d.

e.

加-static才使用静态库

动静态库生成的文件大小不一样

总结:

 

使用外部库

系统中其实有很多库,它们通常由一组互相关联的用来完成某项常见工作的函数构成。比如用来处理屏幕显示情况的函数(ncurses 库)

附加问题

动态库加载-----可执行程序和地址空间

我们的可执行程序,编译成功,没有加载运行,二进制代码中包含了地址

ELF格式的可执行程序,二进制是有自己的固定格式的

(elf可执行程序的头部,可执行程序的属性)

这些地址其实就是虚拟地址(逻辑地址)

ELF+加载器:各个区域的起始和结束地址,main函数的入口地址

进程=内核数据结构+代码和数据

main函数地址入口+pc指针

库没有加载?OS可以让进程知道

库也要先描叙,再组织,被OS管理

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你好,赵志伟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值