前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[040]Linux Storage 入门

[040]Linux Storage 入门

作者头像
王小二
发布2020-06-08 12:07:30
2.6K0
发布2020-06-08 12:07:30
举报

前言

本文大量代码基于linux 0.11,因为早期linux的版本更加适合初学者入门。虽然代码比较早,但是不妨碍我们学习Linux Storage的精髓。

一、hello world

1.1 Demo

代码语言:javascript
复制
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main(int argc, char *argv[]) {
    printf("pid = %d\n", getpid());
    int fd1,fd2;
    char s[] = "hello world\n";
    //打开文件,拿到fd
    fd1 = open("/tmp/1.txt", O_RDWR | O_CREAT);
    printf("fd1 = %d\n", fd1);
    //写入
    write(fd1, s, sizeof(s));
    close(fd1);
    fd2 = open("/tmp/2.txt", O_RDWR | O_CREAT);
    printf("fd2 = %d\n", fd2);
    //打开文件,拿到fd
    fd1 = open("/tmp/1.txt", O_RDWR);
    printf("fd1 = %d\n", fd1);
    char buffer[80];
    //读取
    read(fd1, buffer, sizeof(buffer));
    //关闭fd
    printf("%s", buffer);
    getchar();//暂停程序
    close(fd1);
    close(fd2);
    return 0;
}

运行结果

代码语言:javascript
复制
pid = 14378
fd1 = 3
fd2 = 3
fd1 = 4
hello world

1.1 fd只是一个数字,代表数字和文件之前的一个映射关系

查看/proc/14378/fd,可以看到映射关系

代码语言:javascript
复制
dr-x------ 2 wangbinhong tctnb  0 3月  14 16:27 .
dr-xr-xr-x 9 wangbinhong tctnb  0 3月  14 16:26 ..
lrwx------ 1 wangbinhong tctnb 64 3月  14 16:27 0 -> /dev/pts/19 //stdin
lrwx------ 1 wangbinhong tctnb 64 3月  14 16:27 1 -> /dev/pts/19 //stdout
lrwx------ 1 wangbinhong tctnb 64 3月  14 16:27 2 -> /dev/pts/19 //stderr
lrwx------ 1 wangbinhong tctnb 64 3月  14 16:27 3 -> /tmp/2.txt
lrwx------ 1 wangbinhong tctnb 64 3月  14 16:27 4 -> /tmp/1.txt

程序中的4是数字,/proc/14378/fd/4是一个文件,链接到/tmp/1.txt,/proc/14378/fd/4只存在内存中,不存在硬盘中,程序中的数字4不是指向/proc/14378/fd/4文件

二、数字fd代表什么

2.1 task_struct

每一个进程在内核中的有一个task_struct的结构体,结构体中有一个file指针数组filp,fd代表filp这个数组的index,fd = 4指向当前进程的task_struct结构体中filp[4]指向的file结构体

代码语言:javascript
复制
struct task_struct {
    ...
    struct file * filp[NR_OPEN];
    ...
}

2.2 file

代码语言:javascript
复制
struct file {
    unsigned short f_mode;//文件的类型和属性
    unsigned short f_flags;//文件打开的标志
    unsigned short f_count;//关联的fd的个数
    struct m_inode * f_inode;//file的真实实现
    off_t f_pos;//文件当前的读写指针,读到哪里了。
}

2.3 file_table

内核中还有一个全局的file_table,是file数组,保存所有file结构体。

代码语言:javascript
复制
struct file file_table[NR_FILE];//系统级的一个file table

2.4 关联关系

两个细节 dup:同进程两个fd指向同一个file fork:两个进程的两个fd指向同一个file

Linux 0.11

Linux 4.19

2.5 Binder传输fd

Binder传输fd,两个进程的不同fd指向了同一个file,享有相同的file offset和file status flag.

举例:

进程A的fd是3,通过Binder通信传递给进程B,进程B拿到了fd是4, 这两个fd同时指向同一个file结构体,这个file结构体又指向一个m_inode(1.txt),1.txt文件存储"helloworld"字符 进程A先读5个字符,读到hello 进程B再读5个字符,会读到world,而不是hello

三、以pipe为例:一切皆文件

我的早期错误想法

硬件驱动通过设备文件和用户空间的应用程序通信,将驱动的信息写进设备文件,然后应用程序读取设备文件的内容。

3.1 pipe初始化

pipe的原理图

3.1.1 sys_pipe

用户空间调用pipe的系统调用,会返回两个fd给用户空间

代码语言:javascript
复制
long pipe[2];
long * files = pipe;
pipe(files);

系统调用的pipe实现

代码语言:javascript
复制
int sys_pipe(unsigned long * fildes)//系统调用生成一对pipe
{
    struct m_inode * inode;
    struct file * f[2];
    int fd[2];
    int i,j;
        //寻找两个空闲的file结构体
    j=0;
    for(i=0;j<2 && i<NR_FILE;i++)
        if (!file_table[i].f_count)//找到空闲的file
            (f[j++]=i+file_table)->f_count++;//将空闲的file的f_count+1,并保存这两个file结构体
    if (j==1)//只找到一个
        f[0]->f_count=0;//将第一file重置,清空
    if (j<2)
        return -1;//没找到一队,反正就是失败
        //当前进程中找到两个空闲的index
    j=0;
    for(i=0;j<2 && i<NR_OPEN;i++)//将两个file的指针分别保存到current->filp[i]的空闲处
        if (!current->filp[i]) {
            current->filp[ fd[j]=i ] = f[j];
            j++;
        }
    if (j==1)
        current->filp[fd[0]]=NULL;
    if (j<2) {
        f[0]->f_count=f[1]->f_count=0;
        return -1;
    }//和上面逻辑类似
    if (!(inode=get_pipe_inode())) {//详见3.1.2 获得一个pipe inode
        current->filp[fd[0]] =
            current->filp[fd[1]] = NULL;
        f[0]->f_count = f[1]->f_count = 0;
        return -1;
    }
    f[0]->f_inode = f[1]->f_inode = inode;//让两个file结构体的f_inode指向pipe inode
    f[0]->f_pos = f[1]->f_pos = 0;//重置读写指针
    f[0]->f_mode = 1;       /* read */
    f[1]->f_mode = 2;       /* write */
    put_fs_long(fd[0],0+fildes);//将fd0数值返回给用户空间
    put_fs_long(fd[1],1+fildes);//将fd1数值返回给用户空间
    return 0;
}
3.1.2 get_pipe_inode

创建一个m_inode结构体 将m_inode的i_size指向一块4096B的缓冲区 设置m_inode的i_pipe为1,标识这个m_inode为pipe inode

代码语言:javascript
复制
struct m_inode * get_pipe_inode(void)//返回一个空的pipe inode用于pipe
{
    struct m_inode * inode;

    if (!(inode = get_empty_inode()))
        return NULL;
    if (!(inode->i_size=get_free_page())) {//申请一个物理页4096B作为环形管道缓冲区,缓冲区指针保存到i_size。
        inode->i_count = 0;
        return NULL;
    }
    inode->i_count = 2; /* sum of readers/writers */
    PIPE_HEAD(*inode) = PIPE_TAIL(*inode) = 0;
    inode->i_pipe = 1;//表示为pipe的m_inode
    return inode;
}
3.1.3 get_free_page

申请一个内存页,作为共享缓冲区,并返回内存的地址

代码语言:javascript
复制
unsigned long get_free_page(void)
{
register unsigned long __res asm("ax");

__asm__("std ; repne ; scasb\n\t"
    "jne 1f\n\t"
    "movb $1,1(%%edi)\n\t"
    "sall $12,%%ecx\n\t"
    "addl %2,%%ecx\n\t"
    "movl %%ecx,%%edx\n\t"
    "movl $1024,%%ecx\n\t"
    "leal 4092(%%edx),%%edi\n\t"
    "rep ; stosl\n\t"
    "movl %%edx,%%eax\n"
    "1:"
    :"=a" (__res)
    :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
    "D" (mem_map+PAGING_PAGES-1)
    :"di","cx","dx");
return __res;
}

3.2 读pipe

3.2.1 sys_read

根据inode->i_pip,将sys_read变成read_pipe。

代码语言:javascript
复制
int sys_read(unsigned int fd,char * buf,int count)//文件读的系统调用,fd->file->inode->数据块
{
    struct file * file;
    struct m_inode * inode;

    if (fd>=NR_OPEN || count<0 || !(file=current->filp[fd]))
        return -EINVAL;
    if (!count)
        return 0;
    verify_area(buf,count);
    inode = file->f_inode;
    if (inode->i_pipe)//pipe
        return (file->f_mode&1)?read_pipe(inode,buf,count):-EIO;//3.2.2
    if (S_ISCHR(inode->i_mode))//字符设备
        return rw_char(READ,inode->i_zone[0],buf,count,&file->f_pos);
    if (S_ISBLK(inode->i_mode))//块设备
        return block_read(inode->i_zone[0],&file->f_pos,buf,count);
    if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode)) {//常规文件或目录
        if (count+file->f_pos > inode->i_size)
            count = inode->i_size - file->f_pos;
        if (count<=0)
            return 0;
        return file_read(inode,file,buf,count);
    }
    printk("(Read)inode->i_mode=%06o\n\r",inode->i_mode);
    return -EINVAL;
}
3.2.2 read_pipe

将缓冲区的数据读到用户空间char,读count字节

代码语言:javascript
复制
int read_pipe(struct m_inode * inode, char * buf, int count)//读取pipe
{
    int chars, size, read = 0;

    while (count>0) {
        while (!(size=PIPE_SIZE(*inode))) {//如果发现没有内容
            wake_up(&inode->i_wait);//唤醒写端
            if (inode->i_count != 2) /* are there any writers? *///没有写端
                return read;
            sleep_on(&inode->i_wait);//没有内容就睡眠
        }
        chars = PAGE_SIZE-PIPE_TAIL(*inode);//判断尾部的数据
        if (chars > count)
            chars = count;
        if (chars > size)
            chars = size;
        count -= chars;
        read += chars;
        size = PIPE_TAIL(*inode);//头部开始读的指针
        PIPE_TAIL(*inode) += chars;
        PIPE_TAIL(*inode) &= (PAGE_SIZE-1);
        while (chars-->0)
            put_fs_byte(((char *)inode->i_size)[size++],buf++);
    }
    wake_up(&inode->i_wait);
    return read;
}

3.3 写pipe

3.3.1 sys_write

根据inode->i_pip,将sys_write变成write_pipe。

代码语言:javascript
复制
int sys_write(unsigned int fd,char * buf,int count)//文件写的系统调用,fd->file->inode->数据块
{
    struct file * file;
    struct m_inode * inode;
    
    if (fd>=NR_OPEN || count <0 || !(file=current->filp[fd]))
        return -EINVAL;
    if (!count)
        return 0;
    inode=file->f_inode;
    if (inode->i_pipe)
        return (file->f_mode&2)?write_pipe(inode,buf,count):-EIO;//3.3.2
    if (S_ISCHR(inode->i_mode))
        return rw_char(WRITE,inode->i_zone[0],buf,count,&file->f_pos);
    if (S_ISBLK(inode->i_mode))
        return block_write(inode->i_zone[0],&file->f_pos,buf,count);
    if (S_ISREG(inode->i_mode))
        return file_write(inode,file,buf,count);
    printk("(Write)inode->i_mode=%06o\n\r",inode->i_mode);
    return -EINVAL;
}
3.3.2 write_pipe

将用户空间char对应的数据写到缓冲区,写入count字节

代码语言:javascript
复制
int write_pipe(struct m_inode * inode, char * buf, int count)//写pipe的实现
{
    int chars, size, written = 0;

    while (count>0) {
        while (!(size=(PAGE_SIZE-1)-PIPE_SIZE(*inode))) {//如果写满了,size为0
            wake_up(&inode->i_wait);//唤醒读端
            if (inode->i_count != 2) { /* no readers *///没有读者直接返回
                current->signal |= (1<<(SIGPIPE-1));
                return written?written:-1;
            }
            sleep_on(&inode->i_wait);//写端休眠
        }
        chars = PAGE_SIZE-PIPE_HEAD(*inode);//计算管道头部到缓冲区末端的空闲字节数 4098
        if (chars > count)
            chars = count;
        if (chars > size)
            chars = size;
        count -= chars;
        written += chars;
        size = PIPE_HEAD(*inode);//当前的头指正
        PIPE_HEAD(*inode) += chars;
        PIPE_HEAD(*inode) &= (PAGE_SIZE-1);
        while (chars-->0)
            ((char *)inode->i_size)[size++]=get_fs_byte(buf++);//一个写字符到管道
    }
    wake_up(&inode->i_wait);//写完唤醒读端
    return written;
}

3.4 pipe读写指针

其实pipe的读写指针并没有保存在file结构体,而是保存在m_inode。 如果缓冲区满了,读端一直不读,会导致写端的进程sleep。 整个读写的过程并没有通过锁来控制,而是通过ringbuffer来实现,有兴趣的可以自己研究。

代码语言:javascript
复制
struct m_inode {
...
    unsigned long i_size;//被作为指针指向申请的缓冲区,一个缓冲区4096B
    unsigned short i_zone[9];//用i_zone[0]代表写的游标,用i_zone[1]代表写的游标
...
}

3.5 思考一个问题

父进程创建一对pipe的fd1 fd2 子进程通过fork复制父进程的pipe fd1 fd2 父进程关闭读的fd2 子进程关闭写的fd1 父子进程就可以通过pipe进行跨进程通信

3.6 Linux的改进

文件系统的引入会导致if else越来越多,用file_operations结构体代替大量if else file中f_op保存read write的函数指针

代码语言:javascript
复制
struct file {
    const struct file_operations    *f_op;
    void            *private_data;//这个很重要
} 

struct file_operations {
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
};

我觉得这样子理解更加合适:一切皆文件接口

四、普通文件

4.1重要数据结构

4.1.1 m_inode

在前面的pipe例子中m_inode并没有指向任何的实体文件,我们接下来讨论实体文件的m_inode i_dev:代表块设备号(在那个磁盘上) i_zone[9]:代表数据块号(代表文件的数据区域在磁盘的第几个数据块) 一个数据块是1KB=1024B

代码语言:javascript
复制
struct m_inode {
    unsigned short i_mode;//15-12文件类型,11-9保存执行文件设置,8-0保存文件权限
    unsigned short i_uid;//文件宿主的用户id
    unsigned long i_size;//文件长度
    unsigned long i_mtime;//修改时间
    unsigned char i_gid;//文件宿主的组id
    unsigned char i_nlinks;//硬链接的次数
    unsigned short i_zone[9];//对应数据块 0-6直接是块号,7一次间接块,8二次间接块,一个块是1KB=1024Byte
    //因为块号用short来表示,也就是2Byte,所以一个块可以存放512个块号,所以一次块512个,二次块就是512*512。
    //所以变相的可以算出一个文件的最大size是7+512+512*512 kb
    //一般逻辑块的大小会和buffer_head大小一样。
/* these are in memory also */
    struct task_struct * i_wait;
    unsigned long i_atime;
    unsigned long i_ctime;
    unsigned short i_dev;//设备号
    unsigned short i_num;
    unsigned short i_count;
    unsigned char i_lock;
    unsigned char i_dirt;
    unsigned char i_pipe;
    unsigned char i_mount;
    unsigned char i_seek;
    unsigned char i_update;
};
4.1.2 buffer_head

buffer_head是内存和磁盘的一次数据块交互的载体 b_dev设备号 b_blocknr数据块号 b_data指向一块1024B内存的首地址 所有buffer_head会被存放在一个hash表中,按照设备名,块号,方便重复使用

代码语言:javascript
复制
struct buffer_head {
    char * b_data;          /* pointer to data block (1024 bytes) */
    unsigned long b_blocknr;    /* block number */ //块号
    unsigned short b_dev;       /* device (0 = free) */ //设备号
    unsigned char b_uptodate;
    unsigned char b_dirt;       /* 0-clean,1-dirty */
    unsigned char b_count;      /* users using this block */
    unsigned char b_lock;       /* 0 - ok, 1 -locked */
    struct task_struct * b_wait;
    struct buffer_head * b_prev;
    struct buffer_head * b_next;
    struct buffer_head * b_prev_free;
    struct buffer_head * b_next_free;
};

可以调用下面两个接口,完成数据块的读写,这背后的实现就要看块设备驱动怎么实现的。

先记住,buffer_head(设备号+块号+内存地址)+读写指令 可以完成一次信息的交换。
代码语言:javascript
复制
ll_rw_block(READ,bh);
ll_rw_block(WRITE,bh);

4.2 读文件

4.2.1 file_read

根据(filp->f_pos)/BLOCK_SIZE计算对应的起始块号nr 根据inode->i_dev和nr调用bread获得buffer_head 调用ll_rw_block(READ,bh)请求数据块的数据 将buffer_head中b_data拷贝到用户空间的buf

代码语言:javascript
复制
int file_read(struct m_inode * inode, struct file * filp, char * buf, int count)
{
    int left,chars,nr;
    struct buffer_head * bh;

    if ((left=count)<=0)
        return 0;
    while (left) {
        if (nr = bmap(inode,(filp->f_pos)/BLOCK_SIZE)) {
            if (!(bh=bread(inode->i_dev,nr)))//4.2.2
                break;
        } else
            bh = NULL;
        nr = filp->f_pos % BLOCK_SIZE;
        chars = MIN( BLOCK_SIZE-nr , left );
        filp->f_pos += chars;
        left -= chars;
        if (bh) {
            char * p = nr + bh->b_data;
            while (chars-->0)
                put_fs_byte(*(p++),buf++);
            brelse(bh);
        } else {
            while (chars-->0)
                put_fs_byte(0,buf++);
        }
    }
    inode->i_atime = CURRENT_TIME;
    return (count-left)?(count-left):-ERROR;
}
4.2.2 bread

根据设备号,数据块号,获得一个buffer_head

代码语言:javascript
复制
/*
 * bread() reads a specified block and returns the buffer that contains
 * it. It returns NULL if the block was unreadable.
 */
struct buffer_head * bread(int dev,int block)//从dev,block获得buffer_head,一般用这个就可以
{
    struct buffer_head * bh;

    if (!(bh=getblk(dev,block)))//拿一块空闲的buffer_head
        panic("bread: getblk returned NULL\n");
    if (bh->b_uptodate)
        return bh;
    ll_rw_block(READ,bh);//将硬件的数据读取到buffer_head
    wait_on_buffer(bh);
    if (bh->b_uptodate)
        return bh;
    brelse(bh);//释放锁
    return NULL;
}

4.3 写文件

4.3.1 file_write

根据(filp->f_pos)/BLOCK_SIZE计算对应的块号block,如果文件不够大,需要扩容。 根据inode->i_dev和block调用bread获得buffer_head 将用户空间的buf拷贝到buffer_head中b_data。

代码语言:javascript
复制
int file_write(struct m_inode * inode, struct file * filp, char * buf, int count)
{
    off_t pos;
    int block,c;
    struct buffer_head * bh;
    char * p;
    int i=0;

/*
 * ok, append may not work when many processes are writing at the same time
 * but so what. That way leads to madness anyway.
 */
    if (filp->f_flags & O_APPEND)
        pos = inode->i_size;
    else
        pos = filp->f_pos;
    while (i<count) {
        if (!(block = create_block(inode,pos/BLOCK_SIZE)))//如果写文件的时候发现文件不够大,就要扩容
            break;
        if (!(bh=bread(inode->i_dev,block)))
            break;
        c = pos % BLOCK_SIZE;
        p = c + bh->b_data;
        bh->b_dirt = 1;
        c = BLOCK_SIZE-c;
        if (c > count-i) c = count-i;
        pos += c;
        if (pos > inode->i_size) {
            inode->i_size = pos;
            inode->i_dirt = 1;
        }
        i += c;
        while (c-->0)
            *(p++) = get_fs_byte(buf++);
        brelse(bh);
    }
    inode->i_mtime = CURRENT_TIME;
    if (!(filp->f_flags & O_APPEND)) {
        filp->f_pos = pos;
        inode->i_ctime = CURRENT_TIME;
    }
    return (i?i:-1);
}
4.3.2 sys_sync

写文件,并不会立刻写到磁盘,需要调用系统调用sync ll_rw_block将buffer_head的脏数据同步到块设备

代码语言:javascript
复制
int sys_sync(void)//系统调用,同步块设备和内存高速缓存中数据
{
    int i;
    struct buffer_head * bh;

    sync_inodes();      /* write out inodes into buffers *///将修改的inode数据写入到buffer_head.
    bh = start_buffer;
    for (i=0 ; i<NR_BUFFERS ; i++,bh++) {
        wait_on_buffer(bh);
        if (bh->b_dirt)
            ll_rw_block(WRITE,bh);//真的写到块设备中
    }
    return 0;
}

五、块设备

5.1 内部结构

块设备中数据是按照1KB一个块分区 1KB=8K bits 可以指向8K个inode对象,可以指向8K个数据块,每一个数据块又是1KB。

这是块设备中存储的inode结构体

代码语言:javascript
复制
struct d_inode {  //块设备中对应的inode的结构体
    unsigned short i_mode;
    unsigned short i_uid;
    unsigned long i_size;
    unsigned long i_time;
    unsigned char i_gid;
    unsigned char i_nlinks;
    unsigned short i_zone[9];
};
代码语言:javascript
复制
struct dir_entry { //目录
    unsigned short inode;//inode
    char name[NAME_LEN];//inode对应的文件名
};

5.2 mount("dev/block/sda0","/sdcard")

第一步:从dev/block/sda0中读取第0块inode块上的数据,读取第一个d_inode的数据,并构建内存中的m_inode。

第二步:将m_inode的i_num和"sdcard"按照dir_entry的结构体存放在"/"目录对应m_inode指向的i_zone数据区域

5.3 int fd = open("/sdcard/1.txt")

第一步:找到m_inode("sdcard")

读取"/"对应的m_inode("/")的 i_zone[9]的数据到内存中 根据"sdcard"得到inode号,拿到"sdcard"对应的m_inode("sdcard"),m_inode已经在mount中创建。

第二步:创建m_inode("1.txt")

读取m_inode("sdcard")的 i_zone[9]的数据到内存中 根据"1.txt",拿到1.txt文件对应的d_inode的号 计算d_inode号找到对应的d_inode结构体存放在块设备的块号,块号=inode/一个块最多存放的d_inode结构体 读取的块号对应数据到内存中,读取对应的d_inode数据,构建m_inode("sdcard")

第三步:将fd指向file指向m_inode

创建file指向m_inode("1.txt") file[fd]指向file 返回fd

从此形成fd->file->m_inode的对应关系,write read close 都可以对应的转化成file的操作,对应的m_inode的操作

六、目前Linux的架构

构建了一个VFS层,虚拟文件系统,各类文件系统可以更好的兼容,EXT4,F2FS

文件系统和块设备的数据交互,用BIO代替了buffer_head 因为如果操作一大块数据的IO,需要拆分成很多个buffer_head数据结构体,资源浪费

新增了Block Layer层对BIO进行合并调度

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、hello world
    • 1.1 Demo
      • 1.1 fd只是一个数字,代表数字和文件之前的一个映射关系
      • 二、数字fd代表什么
        • 2.1 task_struct
          • 2.2 file
            • 2.3 file_table
              • 2.4 关联关系
                • 2.5 Binder传输fd
                • 三、以pipe为例:一切皆文件
                  • 我的早期错误想法
                    • 3.1 pipe初始化
                      • 3.2 读pipe
                        • 3.3 写pipe
                          • 3.4 pipe读写指针
                            • 3.5 思考一个问题
                              • 3.6 Linux的改进
                                • 我觉得这样子理解更加合适:一切皆文件接口
                                • 四、普通文件
                                  • 4.1重要数据结构
                                    • 4.2 读文件
                                      • 4.3 写文件
                                      • 五、块设备
                                        • 5.1 内部结构
                                          • 5.2 mount("dev/block/sda0","/sdcard")
                                            • 5.3 int fd = open("/sdcard/1.txt")
                                            • 六、目前Linux的架构
                                            相关产品与服务
                                            文件存储
                                            文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
                                            领券
                                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档