Linux系统编程(二)——Linux系统IO函数

在第一篇的时候写到了系统环境的搭建以及各种调试的方法,接下来讲述关于系统函数的使用。

目录

0x01 标准C库IO函数和Linux系统IO函数对比

一、标准C库IO函数操作流程

二、标准C库IO和Linux系统IO的关系

三、虚拟地址空间

0x02 LinuxIO函数实例

一、open()、read()、write()、lseek()

二、stat()、lstat()

三、access()、change()

四、chmod()、dup()、dup2()

五、fcntl()、mkdir()、truncate()

0x03 获取当前目录下的文件

0x04 模拟实现ls-l命令


0x01 标准C库IO函数和Linux系统IO函数对比

一、标准C库IO函数操作流程

在这里插入图片描述 标准C库的IO函数(第三方库)是可以跨平台进行使用,区别在于调用各个系统的API。C库的每个函数都具有一个缓冲区,因此他的执行效率会更高,写磁盘次数降低,类似于FIFO操作。在网络通信中,这种效率的问题需要更加的关注,所以不可以使用缓冲区,直接调用LinuxIO接口。

二、标准C库IO和Linux系统IO的关系

关系就是调用与被调用的关系,当缓冲区数据满或者是数据空时,内核才会调用一次写或读的操作。那么我们可以看看FILE的结构体声明:

struct _IO_FILE
{
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
  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;
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it's too small.  */

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

我们可以注意到有个指针为buf,指向起始与维护,其实这个就是我们的缓冲区了。还有一个fileno,其实就是文件描述符。

三、虚拟地址空间

在这里插入图片描述

这个虚拟空间是不存在的,避免由于物理内存分配不合理(比如地址不连续),或是当前内存无法存放下一个要处理的内存。这个时候,就需要虚拟地址空间来解决了。这里的虚拟地址空间,是指每个进程自己的虚拟地址空间,其组成如上图,其组成有如下:

  • 程序段(TEXT):程序代码在内存中的映射,存放函数体的二进制代码。
  • 初始化过的数据(DATA):在程序运行初已经对变量进行初始化的数据。
  • 未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。
  • 栈(STACK):存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存,其操作方式类似于数据结构中的栈。
  • 堆(HEAP):存储动态内存分配,需要程序员手工分配,手工释放.注意它与数据结构中的堆是两回事,分配方式类似于链表。

需要注意的是,TEXT、BSS、DATA段在编译时已经决定了进程将占用多少M。也可以通过size来得知这些占用空间的大小。

在正常情况下,Linux进程不能对用来存放程序代码的内存区域执行写操作,即程序代码是以只读的方式加载到内存中,但是它可以被多个进程安全的共享。

虚拟地址是程序运行时,程序访问存储器所使用的逻辑地址称为虚拟地址,通过逻辑地址映射到真正的物理内存上:

  1. 进程虚拟地址空间,也就是意味着认定规定的逻辑地址空间。
  2. 每个进程都可拥有3G的虚拟地址空间,并且用户进程之间的地址是互不可见,互不影响。也就是说即使两个进程对同一块地址进程操作,也不会产生问题。
  3. 虚拟地址是不具备存储能力的,数据的存储依然要存放在物理内存中。
  4. 可以通过页表映射从逻辑地址空间访问真实的物理地址空间。

对于学过单片机的人来说,每次烧录程序时,由于其没有MMU处理单元,更没有操作系统,这个单片机直接操作的内存其实是物理内存。在这种情况下,想要在内存中同时运行两个程序是不可能的。如果第一个程序在2000的位置写入一个新的值,那么将会擦掉第二个程序存放在相同位置上的所有内容,所以同时运行两个程序是根本行不通的,这两个程序会立刻崩溃。

这里的关键问题是我们引用了绝对物理地址,这正是我们最需要避免的。所以吗可以把进程所使用的地址隔开来,即让操作系统为每个进程分配独立的一套虚拟地址,互不干涉。那么如何管理虚拟地址与物理地址之间的关系?

主要有两种方式:内存分段内存分页

(1)内存分段

内存分段:程序是由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性,所以就用分段的形式把这些段分离出来。

在分段机制下,虚拟地址和物理地址是如何映射的?在分段机制下的虚拟地址由两部分组成,一部分是段选择因子和段内偏移量。

段选择因子和段内偏移量:

  • 段选择子就保存在段寄存器里面,段选择子里面最重要的是段号,用作段表的索引。段表里保存的是这个段的基地址、段的界限和特权等级等。
  • 虚拟地址中的段内偏移量应该位于0和段界限之间,如果段内偏移量是合法的,就将基地址加上段内偏移量得到物理内存地址。

分段这个方法确实可以解决程序本身不需要关心具体的物理内存地址的问题,但它的缺点也是很显著的:

  • 第一个就是内存碎片化的问题。
  • 第二个是内存交换的效率低的问题。

那么这个内存碎片化主要分为有:内部内存碎片和外部内存碎片。

在分段中,由于分的每个段的长度是按需分配的,所以有多少的需求就会分配多大的段,所以不会出现内部内成碎片

但是由于每个段的长度都不固定,所以多个段未必能恰好使用所有的内存空间,会产生多个不连续的小物理内存,导致新的程序无法被装载,所以会出现外部内存碎片的问题。

为了解决外部内存碎片问题,所以引出了内存交换。可以把音乐程序占用的那 256MB 内存写到硬盘上,然后再从硬盘上读回来到内存里。不过再读回的时候,我们不能装载回原来的位置,而是紧紧跟着那已经被占用了的 512MB 内存后面。这样就能空缺出连续的 256MB 空间,于是新的 200MB 程序就可以装载进来。

这个内存交换空间,也就是在Linux系统中的Swap空间,这块空间是从硬盘中划分出来的,用于内存与硬盘的空间交换。

最后再说说为什么它效率低。对于多进程的系统来说,用分段的方式,外部内存碎片是容易产生的,产生了外部内存碎片,那不得不重新swap内存区域,这个过程会产生性能瓶颈。因为硬盘的访问速度要比内存慢太多了,每一次内存交换,都需要把一大段的连续的内存数写到硬盘上。所以在进行内存交换的时候,交换的是一个占内存空间很大的程序,这样整个机器会显得很卡顿。所以才出现了内存分页。

(2)内存分页

分段的好处就是能产生连续的内存空间,但是会出现外部内存碎片和内存交换的空间太大的问题。要解决这些问题,那么就要想出能少出现一些内存碎片的办法,当然也需要注意每次交换写入或者从磁盘装载的数据更少一些,这样就可以解决问题了。那么这种方法就叫内存分页。

分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间我们叫作页,在Linux下,每一页的大小为4KB。

虚拟地址与物理地址之间通过页表来映射:

 页表是存储在内存里的,内存管理单元(MMU)就做将虚拟内存地址转换成物理地址的工作。

而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。(也就说这个页表是会进行更新的)

那么这个如何解决外部内存碎片以及内存交换效率低的问题:

内存分页由于内存空间都是预先划分好的,也就不会像内存分段一样,在段与段之间会产生间隙非常小的内存,这正是分段会产生外部内存碎片的原因。而采用了分页,页与页之间是紧密排列的,所以不会有外部碎片。

但是,因为内存分页机制分配内存的最小单位是一页,即使程序不足一页大小,我们最少只能分配一个页,所以页内会出现内存浪费,所以针对内存分页机制会有内部内存碎片的现象。

如果内存空间不够,操作系统会把其他正在运行的进程中的最近没有使用的内存页面给释放掉,也就是暂时写在硬盘上,称为换出。一旦需要时,再加载进来,称为换入。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,内存交换的效率就相对比较高。

那么这样其实,分页的方式使得我们在加载程序的时候,不再需要一次性把程序加载到物理内存中。我们完全可以在进行虚拟内存和物理内存的页之间的映射之后,并不真的把页加载到物理内存里,而是只有在程序运行中,需要用到对应的虚拟内存页里面的指令和数据时,再加载到物理内存里面去。

那么对于内存分页,虚拟地址分为两个部分,页号和页内偏移。页号作为页表的索引,页表包含物理页每页所在的物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存地址。

所以最后的总结,内存分页的流程:

  1. 把虚拟内存地址,切分成页号和偏移量。
  2. 根据页号,从页表里面,查询对应的物理页号。
  3. 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。

 

但是,现实是残酷的,如果这么安排,对于多进程的系统来说,它的页表需要非常庞大,因为它分的太细了。所以我们需要采取一种多级页表的解决方案。 所以对于每个系统,都有一级分页和二级分页,这个思想是计算机组成原理中的局部性原理,在使用的过程中,如果某个一级页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表。

(3)段页式内存管理

内存分段和内存分页并不是对立的,她们是可以组合起来在同一个系统中使用的,那么组合起来后,通常称为段页式内存管理。其实现的方式为:

  • 先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制。
  • 接着再把每个段分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页。

这样,地址结构就由段号、段内页号和页内位移三部分组成。

用于段页式地址变换的数据结构是每一个程序一张段表,每个段又建立一张页表,段表中的地址是页表的起始地址,而页表中的地址则为某页的物理页号。

那么在段页式地址变换中要得到物理地址必须经过三次内存访问:

  • 第一次访问段表,得到页表起始地址;
  • 第二次访问页表,得到物理页号;
  • 第三次将物理页号与页内位移组成,得到物理地址。

0x02 LinuxIO函数实例

对于这些系统调用函数可以使用如下命令进行查看:linux命令——man_Xcn_小企鹅的博客-CSDN博客_linux man

 man 2 open

一、open()、read()、write()、lseek()

 关于open()函数:

int open(const char *pathname, int flags, mode_t mode);

- pathname:要创建的文件的路径

- flags:对文件的操作权限和其他的设置

        - 必选项:O_RDONLY,O_WRONLY,ORDWR 这三个之间是互斥的

        - 可选项:O_CREAT文件不存在,创建新文件。

flags参数是一个int类型的数据,占四个字节,32位,每一位就是一个标志位

-mode:八进制的数,表示创建出新的文件的操作权限

        - rwx 当前所有用户 当前所有用户所在群组 其他用户 一位代表的是二进制 组合起来是八进制 0777权限最大

        -最终权限是:mode &~ umask ,可以直接在终端查看 0002

        0777 -> 111111111

        & 0775 -> 111111101

        ---------------------- 0002取反相与 相当于0777-0002=0775

        111111101

-umask的作用是,抹去某些权限 可以在终端使用umask 0022去修改,只在当前终端有效

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

int main()
{
    //创建一个新的文件
    int fd = open("create.txt",O_RDWR|O_CREAT,0777);

    if(fd==-1)
    {
        perror("open");
    }

    close(fd);

    return 0;
}

关于read()和write()函数:

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

参数:

        -fd 文件描述符,通过open得到,通过这个文件描述符,操作某个文件

        -buf 缓冲区,读取数据存放的地方,数组的地址

        -count 指定的数组大小

返回值:

        - 成功:

                >0 返回实际已经读取到的字节数

                =0 文件已经读取完了

        - 失败:

                -1 并且设置errno

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

参数:

        - fd:文件描述符,open得到,通过这个文件描述符操作某个文件。

        - buf:要往磁盘写入的数据,数组。

        - count:要写的数据的实际的大小。

返回值:

        - 成功:实际写入的字节数

        - 失败:errno

 下面是一个实现文件拷贝:

//实现文件拷贝
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    //通过open打开.txt文件
    int srcfd = open("a.txt",O_RDONLY);
    //创建一个新的文件,拷贝文件
    if(srcfd == -1)
    {
        perror("open");
        return -1;
    }
    
    int destfd = open("cpy.txt",O_WRONLY|O_CREAT,0664);
    if(destfd == -1)
    {
        perror("open");
        return -1;
    }
    //频繁的读写操作
    char buf[1024] = {0};
    int len = 0;
    while((len = read(srcfd,buf,sizeof(buf))>0))
    {
        write(destfd,buf,len);
    }
    //关闭文件
    close(destfd);
    close(srcfd);

    return 0;
}

关于lseek()函数:

//系统库,通过文件描述符操作

#include <sys/types.h>

#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

参数

        -fd open得到的文件描述符,通过这个fd操作某个文件

        -offset 偏移量

        -whence

                SEEK_SET 设置文件指针的偏移量

                SEEK_CUR 设置偏移量,当前位置+第二个参数offset的值

                SEEK_END 设置偏移量,文件大小+第二个参数offset的值

返回值:

        返回文件指针当前位置

作用:

        1.移动文件指针到头文件 lseek(fd,0,SEEK_SET)

        2.获取当前文件指针的位置 lseek(fd,0,SEEK_CUR)

        3.可以获取文件长度 lseek(fd,0,SEEK_END)

        4.拓展文件长度 lseek(fd,100,SEEK_END),需要写一次数据

//标准C库

#include <stdio.h>

int fseek(FILE *stream, long offset, int whence);

 可以通过这个函数来扩展文件长度:

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

int main()
{
    int fd = open("hello.txt",O_RDWR);

    if(fd==-1)
    {
        perror("open");
        return -1;
    }

    // expand
    int ret = lseek(fd,100,SEEK_END);
    if(ret==-1)
    {
        perror("lseek");
        return -1;
    }
    //写入空数据
    write(fd," ",1);

    close(fd);

    return 0;

}

二、stat()、lstat()

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

int stat(const char *pathname, struct stat *statbuf);

作用:获取一个文件相关的信息(软链接后指向软连接指向的那个文件)

参数:

        - pathname:要操作的文件路径

        - statbuf:结构体变量,传出参数,用于保存获取到的文件信息

返回:

        0 成功

        -1 失败

int lstat(const char *pathname, struct stat *statbuf);

        区别:软连接前的那个文件

 在进入程序前可以看看这个结构体:

这里是操作这个文件的结构体stat,对于每个文件,其都有这么一个结构体可供我们一些信息。并且如果我们需要进行软连接的时候,可以使用如下指令:

ln -s a.txt b.txt

 那么我们现在可以对一个文件进行软连接,并且打印处这个文件的东西:

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

int main()
{
    struct stat statbuf;
    int res = stat("a.txt",&statbuf);
    if(res == -1)
    {
        perror("stat");
        return -1;
    }

    printf("size:%ld\n",statbuf.st_size);
    return 0;
}

三、access()、change()

#include <unistd.h>

int access(const char *pathname, int mode);

作用:判断某个文件是否有某个权限,或者文件是否存在

参数:

        -pathname 文件路径

        - mode

                R_OK:判断是否有读权限

                W_OK:判断是否有写权限

                X_OK:判断是否有执行权限

                F_OK:判断文件是否存在

返回值:

        0 成功

        -1 失败

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

int main()
{
    int ret = access("a.txt",F_OK);
    if(ret == -1)
    {
        perror("access");
    }
    printf("have file!\n");
    return 0;
}

#include <unistd.h>

int chdir(const char *path);

作用:修改进程的工作目录

        比如在/home/..启动了一个可执行程序a.out,进程的工作目录是/home/....

参数:

        -path 需要修改的工作目录

返回值:

        0 成功

        -1 失败

#include <unistd.h>

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

作用:获取当前工作目录

参数:

        -buf:存储的路径,指向的是一个数组(传出参数)

        -size:数组的大小

返回值:

        返回指向的一块内存,这个数据就是第一个参数 

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

int main()
{
    //获取当前的工作目录
    char buf[128];
    getcwd(buf,sizeof(buf));
    printf("当前的工作目录是:%s\n",buf);

    //修改工作目录
    int ret = chdir("/home/zhengxiting/");
    if(ret==-1)
    {
        perror("chdir");
        return -1;
    }
    //创建新的文件
    int fd = open("chdir.txt",O_CREAT|O_RDWR,0664);
    if(fd == -1)
    {
        perror("open");
        return -1;
    }
    close(fd);

    //获取当前的工作目录
    //这个路径根据上面的修改而改变了
    char buf1[128];
    getcwd(buf1,sizeof(buf1));
    printf("当前的工作目录是:%s\n",buf);
    

    return 0;
}

四、chmod()、dup()、dup2()

#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);

作用:用来修改文件的权限

参数:

        - pathname:需要修改的文件路径

        - mode:需要修改的权限值,八进制的数

返回值:

        0 成功

        -1 失败

#include <unistd.h>

int chown(const char *pathname, uid_t owner, gid_t group);

作用:用来修改所在组、所有者的id

可以使用vim /etc/passwd查看所有者id,可以使用vim /etc/group所在组的id

可以使用sudo Useradd xxx 去添加用户

#include <sys/stat.h>
#include <stdio.h>

int main()
{
    int ret = chmod("a.txt",0775);

    if(ret==-1)
    {
        perror("chmod");
        return -1;
    }
    return 0;
}

#include <unistd.h>

int dup(int oldfd);

        作用:拷贝文件描述符。

        fd=3,int fd1 = dup(fd),

        fd指向的是a.txt,fd1也是指向a.txt

        从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符

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

int main()
{
    int fd = open("a.txt",O_RDWR|O_CREAT,0664);

    int fd1 = dup(fd);

    if(fd1==-1)
    {
        perror("dup");
        return -1;
    }

    printf("fd:%d,fd1:%d\n",fd,fd1);

    close(fd);

    char *str="hello world";
    int ret = write(fd1,str,strlen(str));
    if(ret==-1)
    {
        perror("write");
        return -1;
    }
    close(fd1);

    return 0;
}

#include <unistd.h>

int dup2(int oldfd, int newfd);

        作用:用于重定向文件描述符

                 oldfd指向a.txt,newfd指向b.txt

                调用函数成功后,newfd和b.txt做close,newfd指向了a.txt

                oldfd必须是一个有效的文件描述符

                oldfd和newfd值相同,相当于什么都没有做

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

int main()
{
    int fd = open("1.txt",O_RDWR|O_CREAT,0664);
    if(fd==-1)
    {
        perror("open");
        return -1;
    }

    int fd1 = open("2.txt",O_RDWR|O_CREAT,0664);
    if(fd1==-1)
    {
        perror("open");
        return -1;
    }
    printf("fd:%d,fd1:%d\n",fd,fd1);
    int fd2 = dup2(fd,fd1);
    if(fd2==-1)
    {
        perror("dup2");
        return -1;
    }

    //通过fd1去写数据,实际操作的是1.txt,而不是2.txt
    char *str = "Hello dup2";
    int len = write(fd1,str,strlen(str));

    if(len==-1)
    {
        perror("write");
        return -1;
    }

    //fd1与fd2相同
    printf("fd:%d,fd1:%d,fd2:%d\n",fd,fd1,fd2);

    close(fd);
    close(fd1);

    return 0;
}

五、fcntl()、mkdir()、truncate()

#include <unistd.h>

#include <fcntl.h>

int fcntl(int fd, int cmd, ... (可变参数));

参数:

        -fd 需要操作的文件描述符

        -cmd 表示对文件描述符进行如何操作

                -F_DUPFD: 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)

                int ret = fcntl(fd,F_DUPFD)

                -F_GETF:获取指定的文件描述符文件状态flag。

                        获取的flag和通过open函数传递的flag是一个东西。

                -F_SETFL:设置文件描述符状态flag

                        必选项:O_RDONLY,O_WRONLY,O_RDWR不可以修改

                        可选项:O_APPEND(文件中追加数据)

                                      O_NONBLOCK(设置成非阻塞) 

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

int main()
{
    // //复制文件描述符
    // int fd = open("1.txt",O_RDONLY);

    // int ret = fcntl(fd,F_DUPFD);

    //修改或者获取文件状态的flag
    int fd = open("1.txt",O_RDWR);
    if(fd==-1)
    {
        perror("open");
        return -1;
    }
    //获取文件描述符状态flag
    int flag = fcntl(fd,F_GETFL);
    if(flag==-1)
    {
        perror("fcntl");
        return -1;
    }
    flag |= O_APPEND;   //保留原来的flag,不要被覆盖
    //修改文件描述符状态的flag,给flag加入O_APPEND这个标记
    int ret = fcntl(fd,F_SETFL,flag);
    if(ret==-1)
    {
        perror("fcntl");
        return -1;
    }
    char *str = "Hello";
    write(fd,str,strlen(str));
    close(fd);
    return 0;
}

#include <sys/stat.h>

#include <sys/types.h>

int mkdir(const char *pathname, mode_t mode);

作用:创建一个目录

参数:

        -pathname:创建的目录的名称或是路径

        -mode:权限(八进制的数)

返回值:

        0 成功

        -1 失败

#include <unistd.h>

int rmdir(const char *pathname);

作用:删除目录

#include <stdio.h>

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

作用:更改目录名称

返回值:

        0 成功

        -1 返回

#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>

int main()
{
    //创建目录
    int ret = mkdir("aaa",0777);

    if(ret==-1)
    {
        perror("mkdir");
        return -1;
    }

    int oo = rename("aaa","bbb");
    if(oo==-1)
    {
        perror("rename");
        return -1;
    }
    return 0;
}

#include <unistd.h>

#include <sys/types.h>

int truncate(const char *path, off_t length);

作用:缩减、扩展文件的尺寸至指定的大小

参数:

        -path :需要修改文件的路径

        -length:需要最终文件变成的大小

返回值:

        0 成功

        -1 失败

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main()
{
    int ret = truncate("b.txt",20);

    if(ret==1)
    {
        perror("truncate");
        return -1;
    }

    return 0;
}

0x03 获取当前目录下的文件

这里需要用到函数opendir以及使用readdir结构体:

#include <sys/types.h>

#include <dirent.h>

DIR *opendir(const char *name);

作用:打开目录流,返回一个指向目录流的指针,会自自动往后移

参数:

        -name :需要打开的目录的名称

返回值:

        DIR *:理解为目录流信息,对用户没有开放

        错误返回NULL

#include <dirent.h>

struct dirent *readdir(DIR *dirp);

作用:读取目录,参数为上面的返回值。

参数:

        -dirp :是opendir返回的结果

返回值:

        struct dirent *代表的是读取到的文件信息

        struct dirent

        {

                //此目录进入点的iNode

                ino_t d_ino;

                //目录文件开头至此目录进入点的位移

                off_t d_off;

                //d_name长度,不包含NULL字符

                unsigned short int d_reclen;

                //d_name所指的文件类型

                unsigned char d_type; --DT_BLK 块设备 DT_CHR 字符设备 DT_DIR 目录 DT_LNK 软连接 DT_FIFO 管道 DT_REG 普通文件 DT_SOCK 套接字 DT_UNKNOWN 未知

                //文件名

                char d_name[256];

        }

读取到了末尾或者失败了,返回NULL

#include <sys/types.h>

#include <dirent.h>

int closedir(DIR *dirp);

作用:关闭目录

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

int getfilenum(const char* path);

//读取某个目录下所有的普通文件的个数
int main(int argc,char* argv[])
{
    if(argc<2)
    {
        printf("%s path\n",argv[0]);
        return -1;
    }

    int num = getfilenum(argv[1]);

    printf("普通文件个数为:%d\n",num);
    
    return 0;
}

//用于获取目录下所有普通文件的个数
int getfilenum(const char* path)
{
    //打开目录
    DIR * dir = opendir(path);
    if(dir==NULL)
    {
        perror("opendir");
        exit(0);
    }
    struct dirent* ptr;
    //记录普通文件个数
    int total = 0;
    while((ptr=(readdir(dir)))!=NULL)
    {
        //获取名称
        char* dname = ptr->d_name;

        //忽略 . ..
        if(strcmp(dname,".")==0||strcmp(dname,"..")==0)
        {
            continue;
        }
        if(ptr->d_type == DT_DIR)
        {
            //目录,需要继续读取这个目录
            char newpath[256];
            sprintf(newpath,"%s/%s",path,dname);
            total+=getfilenum(newpath);
        }

        if(ptr->d_type == DT_REG)
        {
            // 普通文件
            total++;
        }
    }

    closedir(dir);

    return total;
    
}

0x04 模拟实现ls-l命令

可以使用stat结构体实现,stat的结构体下的宏定义代表的含义:

//模拟实现ls-l指令
//drwxr-xr-x  2 zhengxiting zhengxiting 4096 1月  31  2022  公共的
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <string.h>

int main(int argc,char * argv[])
{
    //判断输入的参数是否正确
    if(argc<2)
    {
        printf("%s filename\n",argv[0]);
        return -1;
    }

    //通过stat获取用户文件的信息
    struct stat st;
    int ret = stat(argv[1],&st);
    if(ret == -1)
    {
        perror("stat");
        return -1;
    }

    //获取文件类型以及访问权限
    char perms[11] = {0};
    switch (st.st_mode&__S_IFMT)
    {
    case __S_IFLNK:
        perms[0] = 'l';
        break;
    case __S_IFDIR:
        perms[0] = 'd';
        break;
    case __S_IFREG:
        perms[0] = '-';
        break;
    case __S_IFBLK:
        perms[0] = 'b';
        break;
    case __S_IFCHR:
        perms[0] = 'c';
        break;
    case __S_IFSOCK:
        perms[0] = 's';
        break;
    case __S_IFIFO:
        perms[0] = 'P';
        break;   
    default:
        perms[0] = '?';
        break;
    }

    //文件所有者
    perms[1] = (st.st_mode&S_IRUSR)?'r':'-';
    perms[2] = (st.st_mode&S_IWUSR)?'w':'-';
    perms[3] = (st.st_mode&S_IXUSR)?'x':'-';

    //文件所在组
    perms[4] = (st.st_mode&S_IRGRP)?'r':'-';
    perms[5] = (st.st_mode&S_IWGRP)?'W':'-';
    perms[6] = (st.st_mode&S_IXGRP)?'X':'-';

    //其他人
    perms[7] = (st.st_mode&S_IROTH)?'r':'-';
    perms[8] = (st.st_mode&S_IWOTH)?'W':'-';
    perms[9] = (st.st_mode&S_IXOTH)?'X':'-';

    //硬链接数
    int linknum = st.st_nlink;

    //文件所有者
    char* fileUser = getpwuid(st.st_uid)->pw_name;

    //文件所在组
    char* fileGrp = getgrgid(st.st_gid)->gr_name;

    //文件大小
    long int fileSize = st.st_size;

    //获取修改时间
    char* time = ctime(&st.st_mtime);
    //将换行符去掉,使用strlen不读换行符
    char mtime[512] = {0};
    strncpy(mtime,time,strlen(time)-1);

    //输出
    char buf[1024];

    sprintf(buf,"%s %d %s %s %ld %s %s",perms,linknum,fileUser,fileGrp,fileSize,mtime,argv[1]);

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

    return 0;
}

猜你喜欢

转载自blog.csdn.net/Alkaid2000/article/details/128037906