Linux系统编程---文件系统

一、文件存储

一个文件主要由两部分组成,dentry(目录项)inode

inode本质是结构体,存储文件的属性信息,如:权限、类型、大小、时间、用户、盘块位置…

也叫做文件属性管理结构,大多数的inode都存储在磁盘上。

少量常用、近期使用的inode会被缓存到内存中。

所谓的删除文件,就是删除inode,但是数据其实还是在硬盘上,以后会覆盖掉。

二、文件操作

1. stat函数:获取文件属性,(从inode结构体中获取)

int stat(const char *path, struct stat *buf);

参数:

        path: 文件路径

        buf:(传出参数) 存放文件属性,inode结构体指针。

返回值:

        成功: 0

        失败: -1 errno

获取文件大小: buf.st_size

获取文件类型: buf.st_mode

获取文件权限: buf.st_mode

符号穿透:stat会。lstat不会。

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

int main(int argc,char *argv[])
{
    struct stat sbuf;
    int ret = stat(argv[1], &sbuf);
    if(ret == -1)
    {
        perror( "stat error" );
        exit(1);
    }

    printf( "file size: %ld\n" , sbuf.st_size);

    return 0;
}

查看f.c文件大小执行:

./mystat f.c

2. lstat:查看文件类型,用法同stat

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

int main(int argc,char *argv[])
{
        struct stat sbuf;
        int ret = lstat(argv[1],&sbuf);
        if (ret == -1)
        {
                perror("stat error");
                exit(1);
        }

        if (S_ISREG(sbuf.st_mode)){
                printf("It's a regular file\n");
        }else if(S_ISDIR(sbuf.st_mode)){
                printf("It's a dir\n");
        }else if (S_ISFIFO(sbuf.st_mode)){
                printf("It's a pipe\n");
        }else if(S_ISLNK(sbuf.st_mode)){
                printf("It's a sym link\n");
        }
        return 0;
}

stat会拿到符号链接指向那个文件或目录的属性---符号穿透

查看符号链接文件时不想让其穿透则使用lstat函数

首先对test.c创建一个软连接

ln -s test.c test.s/test.soft

查看改文件类型,执行以下代码:

./mystat test.s/test.soft

out:

It's a sym link

如果使用stat函数,则以上输出为:

It's a dir/It's a regular file

3.link和unlink隐式回收

int link(const char *oldpath, const char *newpath);

link 函数用于创建一个现有文件的新的硬链接。硬链接,不同于符号链接(软链接),是一个指向文件系统中某个 inode 的直接指针

参数
  • oldpath: 现有文件的路径。
  • newpath: 新链接的路径。
返回值
  • 成功: 返回0。
  • 失败: 返回-1。

int unlink(const char *pathname);

unlink 函数用于删除一个文件的硬链接。

  • unlink是删除一个文件的目录项dentry,使【硬链接数-1】
  • unlink函数的特征:如果这个链接是文件的最后一个链接(即文件的链接数变为0),文件将被删除,并释放占用的资源。如果文件被某个进程打开,它将保持打开状态直到最后一个文件描述符被关闭。

注意事项

  • 硬链接不能跨文件系统创建。
  • 不能为目录创建硬链接,以避免创建可能的循环结构,这可能会对文件系统的完整性造成破坏。
  • 符号链接(软链接)可以链接到目录,并可以跨文件系统链接。

linkunlink 函数的使用示例:

#include <stdio.h>
#include <unistd.h>

int main() {
    char *original = "/path/to/original/file.txt";
    char *linkname = "/path/to/link/file.txt";

    // 创建一个硬链接
    if (link(original, linkname) == -1) {
        perror("link");
        return 1;
    } else {
        printf("Hard link created successfully\n");
    }

    // 删除硬链接
    if (unlink(linkname) == -1) {
        perror("unlink");
        return 1;
    } else {
        printf("Hard link removed successfully\n");
    }

    return 0;
}

编程实现mv命令的改名操作:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc,char *argv[])
{
        link(argv[1],argv[2]);
        unlink(argv[1]);
        return 0;
}

对tets.c改为tets1.c执行以下命令:

./mymv test.c test1.c

三、目录操作

1. getcwd函数

char *getcwd(char *buf, size_t size);

功能:获取当前进程的工作目录
参数:
        buf :缓冲区,存储当前的工作目录

        size :缓冲区大小
返回值:
        成功:buf中保存当前进程工作目录位置

        失败:NULL

2. chdir函数

int chdir(const char *path);

功能:修改当前进程(应用程序)的路径

参数:
        path:切换的路径

返回值:
        成功:0

        失败: -1

示例:

int main (void)
{
    int ret = -l;
    char buf[SIZE] ;

    //1、获取当前进程的工作目录memset(buf, 0, SIZE);
    if (NULL =getcwd(buf, SIZE))
    {
        perror("getcwd error");
        return 1;
    }

    printf( " buf: %s \n", buf);

    //2.改变当前进程的工作目录
    ret = chdir( "/home/deng");
    if (-1 ==ret)
    {
        perror ("chdir error");
        return 1;
    }

    //3.获取当前进程的工作目录memset(buf,0,SIZE);
    if (NULL == getcwd(buf,SIZE))
    {
        perror ("getcwd error");
        return 1;
    }

    printf("buf: %s\n" , buf);
}

3. opendir函数

DIR *opendir(const char *name) ;

功能:打开一个目录
参数:
        name:目录名

返回值:
        成功:返回指向该目录结构体指针

        失败:NULL

4. closedir函数

int closedir(DIR *dirp);

功能:关闭目录
参数:
        dirp: opendir返回的指针

返回值:
        成功:0

        失败: -1

示例:

//目录打开和关闭
int main(void)
{
    DIR *dir = NULL;

    //1.打开日录
    dir = opendir("test");
    if (NULL == dir)
    {
        perror ("opendir error");
        return l;
    }

    closedir(dir);
    return 0;
}

5. readdir函数

struct dirent *readdir(DIR *dirp);

功能:读取目录
参数:
        dirp: opendir的返回值

返回值:
        成功:目录结构体指针

        失败:NULL

相关结构体说明:

struct dirent{
        ino_t d_ino;        //此目录进入点的inode
        off_t d_off;        //目录文件开头至此目录进入点的位移
        signed short int d_reclen;        // d_name 的长度,不包含NULL字符
        unsigned char d_type;        // d_type所指的文件类型
        char d_name [256];        //文件名

};

实现 ls -a 代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<dirent.h>

int main(int argc,char *argv[])
{
        DIR * dp;
        dp = opendir(argv[1]);
        if (dp == NULL){
                perror("opendir error");
                exit(1);
        }

        struct dirent *sdp;
        while((sdp = readdir(dp)) != NULL){
                printf("%s\t",sdp->d_name);
        }

        printf("\n");   

        closedir(dp);

        return 0;
}

在当前目录下使用命令,执行:./myls ./ 即展示当前目录下的文件及大小

 四、递归遍历目录

任务需求:使用opendir closedir readdir stat实现一个递归遍历目录的程序

输入一个指定目录,默认为当前目录。递归列出目录中的文件,同时显示文件大小。

思路分析

递归遍历目录:ls-R.c

1. 判断命令行参数,获取用户要查询的目录名。 int argc, char *argv[1]

                argc == 1 --> ./

2. 判断用户指定的是否是目录。 stat S_ISDIR(); --> 封装函数 isFile() { }

3. 读目录: read_dir() {

                   opendir(dir)

                   while (readdir()){

                        普通文件,直接打印

                        目录:

                                拼接目录访问绝对路径。sprintf(path, "%s/%s", dir, d_name)

                                递归调用自己。--> opendir(path) readdir closedir

                     }

                     closedir()

                     }

        read_dir() --> isFile() ---> read_dir()

代码示例:

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

#define PATH_LEN 256
//dir=/homeitgan/linux  fcn=isfile

void fetchdir(const char *dir,void (*fcn)(char *))
{
        char name[PATH_LEN];
        struct dirent *sdp;
        DIR *dp;

        if ((dp = opendir(dir)) == NULL){  //打开目录失败
                //perror("fetchdir can't open");
                fprintf(stderr,"fetchdir:can't open %s\n",dir);
                return;
        }
        while ((sdp = readdir(dp)) != NULL){
                if (strcmp(sdp->d_name,".") == 0  || strcmp(sdp->d_name,"..") == 0){       //防止出现无限递归
                continue;
                }

                if (strlen(dir)+strlen(sdp->d_name)+2>sizeof(name)){
                fprintf(stderr,"fetchdir:name %s %s too long\n",dir,sdp->d_name);
                }else{
                sprintf(name,"%s/%s",dir,sdp->d_name);
                (*fcn)(name);
                }
        }
        closedir(dp);
}

void isfile(char *name)
{
        struct stat sbuf;

        if(stat(name,&sbuf) == -1){
                fprintf(stderr,"isfile:can't access %s\n",name);
                exit(1);
        }
        if ((sbuf.st_mode & S_IFMT) == S_IFDIR){
                fetchdir(name,isfile);
        }
        printf("%8ld %s\n",sbuf.st_size,name);
}


int main(int argc,char *argv[])
{
        if(argc == 1)
                isfile(".");
        else
                while (--argc > 0) //可一次查询多个目录
                        isfile(*++argv);//循环调用该函数处理各个命令行传入的目录
        return 0;
}

执行 ./ls_R test.c 可查看test.c的文件大小

执行 ./ls_R 可查看当前目录下所有文件的大小

五、文件描述符复制

1. dup和dup2

用来做重定向,本质就是复制文件描述符

重定向:

cat myls.c > out        将myls.c的东西读出再写到out文件中

cat myls.c >> out      将myls.c的东西读出再追加到out文件中

dup()和dup2()用来做重定向,本质就是复制文件描述符,使新的文件描述符也标识旧的文件描述符所标识的文件;

这个过程类似于现实生活中的配钥匙,一把钥匙对应一把锁,然后我们又去配了一把新钥匙,此时两把钥匙都可以打开锁,而dup()和dup2()也一样,原来的文件描述符和新复制出来的文件描述符都指向同一个文件,我们操作这两个文件描述符的任何一个 都能操作它所对应的文件

dup函数

int dup(int oldfd);

功能:
        通过oldfd复制出一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符,最终oldfd和新的文件描述符都指向同一个文件。
参数:
        oldfd :需要复制的文件描述符oldfd

返回值:
        成功:新文件描述符

        失败:-1

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>

int main(int argc,char *argv[])
{
        int fd = open("./out",O_RDONLY);//012 ---3
        
        int newfd = dup(fd);//4

        printf("newfd = %d\n",newfd);

        return 0;
}

make之后执行输出为:newfd = 4

dup2函数:

int dup2(int oldfd,int newfd);

功能:
        通过oldfd 复制出一个新的文件描述符newfd,如果成功,newfd和函数返回值是同一个返回值,最终oldfd和新的文件描述符newfd都指向同一个文件。
参数:
        oldfd :需要复制的文件描述符
        newfd :新的文件描述符,这个描述符可以人为指定一个合法数字(0 - 1023),如果指定的数字已经被占用(和某个文件有关联),此函数会自动关闭close()断开这个数字和某个文件的关联,再来使用这个合法数字。
返回值:
        成功:返回newfd

        失败:返回-1

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>

int main(int argc,char *argv[])
{
        int fd1 = open(argv[1],O_RDWR);//012---3

        int fd2 = open(argv[2],O_RDWR);//0123---4
        
        int fdret = dup2(fd1,fd2);//返回新文件描述符fd2

        printf("fdret = %d\n",fdret);
        
        int ret = write(fd2,"1234567",7);//写入fd1指向的文件
        
        printf("ret = %d\n",ret);
        
        dup2(fd1,STDOUT_FILENO);//将屏幕输入重定向给fd1所指向的文件

        printf("-------------------886\n");

        return 0;
}

首先新建2个空白文件out和out1

make之后执行以下命令:

./dup2 out out1

首先1234567会写入out文件中,其次再将-------------------886写入out文件

int fdret = dup2(fd1,fd2);        其中fd1 = 3,fd2 = 4

把3拷贝给4,即把4指向3(out文件)

dup2(fd1,STDOUT_FILENO);        STDOUT_FILENO = 1

把3拷贝给1,即把1指向3(out文件)

此时指向out的文件描述符有三个

2. fcntl函数

  • fcntl用来改变一个【已经打开】的文件的 访问控制属性
  • 重点掌握两个参数的使用, F_GETFL,F_SETFL

int fcntl(int fd, int cmd, ...)

参数:

        fd 文件描述符

        cmd:指定要执行的操作类型。这个参数可以取多个值,常见的有:

                获取文件状态: F_GETFL

                设置文件状态: F_SETFL       

                获取文件描述符:F_GETFD

                设置文件描述符:F_SETFD

                复制文件描述符:F_DUPFD

                获取文件锁定状态:F_GETLK

                设置文件锁定状态(非阻塞):F_SETLK

                设置文件锁定状态(阻塞):F_SETLKW

        int flgs = fcntl(fd, F_GETFL);

        flgs |= O_NONBLOCK

        fcntl(fd, F_SETFL, flgs);

返回值: 

        成功:根据 cmd 操作的不同,返回值也不同(例如,F_GETFL 返回文件状态标志)。

        失败:返回 -1 并设置 errno 以指示错误原因。

使用 fcntl() 来设置文件为非阻塞模式:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main() {
    int fd, flags;

    // 打开文件
    fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("Open file");
        exit(EXIT_FAILURE);
    }

    // 获取当前的文件状态标志
    flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl - F_GETFL");
        exit(EXIT_FAILURE);
    }

    // 添加非阻塞标志
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) {
        perror("fcntl - F_SETFL");
        exit(EXIT_FAILURE);
    }

    // 继续做其他工作,比如读取文件

    close(fd);
    return 0;
}

终端文件默认是阻塞读的,这里用fcntl将其更改为非阻塞读:

#include <unistd.h>  
#include <fcntl.h>  
#include <errno.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
  
#define MSG_TRY "try again\n"  
  
int main(void)  
{  
    char buf[10];  
    int flags, n;  
  
    flags = fcntl(STDIN_FILENO, F_GETFL); //获取stdin属性信息  
    if(flags == -1){  
        perror("fcntl error");  
        exit(1);  
    }  
    flags |= O_NONBLOCK;  
    int ret = fcntl(STDIN_FILENO, F_SETFL, flags);  
    if(ret == -1){  
        perror("fcntl error");  
        exit(1);  
    }  
  
tryagain:  
    n = read(STDIN_FILENO, buf, 10);  
    if(n < 0){  
        if(errno != EAGAIN){          
            perror("read /dev/tty");  
            exit(1);  
        }  
        sleep(3);  
        write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));  
        goto tryagain;  
    }  
    write(STDOUT_FILENO, buf, n);  
  
    return 0;  
}  

 fcntl实现dup描述符:

int fcntl(int fd, int cmd, ....);

cmd: F_DUPFD

参3:         

        被占用的,返回最小可用的。

        未被占用的, 返回=该值的文件描述符。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<pthread.h>

int main(int argc, char *argv[])
{
        int fd1 = open(argv[1],O_RDWR);

        printf("fd1 = %d\n",fd1);

        int newfd = fcntl(fd1,F_DUPFD,0);//0被占用,fcntl使用文件描述符表中可用的最小文件描述符返回
        printf("newfd = %d\n",newfd);

        int newfd2 = fcntl(fd1,F_DUPFD,7);//7未被占用,返回>=7的文件描述符
        printf("newfd2 = %d\n",newfd2);

        int ret = write(newfd2,"YYYYYYYYYYYYY",7);//只能写入7个
        printf("ret = %d\n",ret);

        return 0;
}

 make之后执行:

./fcntl_dup out

out:

fd1 = 3
newfd = 4
newfd2 = 7
ret = 7

  • 40
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值