操作系统---文件管理

一、系统调用(系统API)

什么是系统调用
  • 由操作系统向应用程序提供的程序接口信息,本质上就是应用程序与操作系统之间交互的接口。

  • 操作系统的主要功能是为了管理硬件资源和为应用软件的开发人员提供一个良好的环境,使得应用程序具有更好的兼容性,为了达到这个目的,内核提供一套统一的具有一定功能的内核接口函数,称为系统调用\系统函数,以C语言函数的格式提供给用户

  • 操作系统负责把应用程序的请求传给内核,由内核调用具体内核功能完成所需请求,结束后把结果通过操作系统的接口函数的返回值传递给调用者

  • UNIX\Linux大部分系统中的系统功能都是通过系统调用来实现的,相当于调用系统函数,但是它们不是真正意义的函数,当调用它们时,进程立即进入内核态,当调用结束,会重新转入回用户态

time ./<可执行文件名> #   统计该进程的时间分布
real    0m0.003s    #   总时间
user    0m0.001s    #   用户态执行时间
sys     0m0.000s    #   内核态执行之间
总时间 = 用户态 + 内核态 + 用户态\内核态切换耗时 + 休眠时间
​
strace ./可执行文件名  #  追踪函数的底层调用过程,以此了解标准库函数中用了哪些系统调用
普通函数与系统函数(系统调用)的区别:
普通函数的调用步骤:
  • 调用者会把要传递的参数压入栈内存

  • 根据函数名也就是该函数的代码段地址,跳转到该函数代码段中执行

  • 从栈内存中弹出传递的参数数据

  • 定义的相关局部变量会入栈到该函数的栈内存进行扩展,并执行相关代码

  • 返回执行结果

  • 销毁该函数的栈内存

  • 返回调用语句处继续执行

系统函数的调用过程:
  • 当执行到系统函数的位置时,会触发软件中断机制

  • 然后进程会转入内核态执行,由内核负责把参数从用户空间拷贝到内核空间

  • 然后内核根据中断编号来执行相应的操作

  • 等执行完毕后,内核再把执行结果从内核空间再拷贝回用户空间中

  • 返回到中断触发位置,转换回用户态继续执行进程

二、一切皆文件

  • 在UNIX\Linux系统中,操作系统把所有服务、设备都抽象成了文件,因为这样可以给各种设备、服务提供同一套统一而简单的操作接口,程序就可以像访问普通文件一样,控制串口、网络、打印机等设备

  • 因此在UNIX\Linux系统中对文件就具有特别重要的意义,一切皆文件,所以大多数情况下,只需要五个基本系统函数操作 open/close/read/write/ioctl,既可以完成对各种设备的输入、输出控制

文件分类:
  • 普通文件 - 包括文本文件、二进制文件、各种压缩包文件等

  • 目录文件 d 类似Windows的文件夹

  • 块设备文件 b 用于保存大块数据的设备,例如磁盘

  • 字符设备文件 c 用于对字符处理的服务、设备,例如 键盘

  • 链接文件 l 类似于Windows的快捷方式

  • 管道文件 p 用于早期进程通信

  • Socket文件\套接字文件 s 用于网络通信

三、文件描述符

什么是文件描述符:
  • 文件描述符是一种非负的整数,用于表示一个打开了的文件

  • 由系统调用(open\creat)返回值,在后续操作文件时可以被内核空间引用去操作对应的文件

  • 它代表了一个内核对象(类似于FILE*),因为内核不能暴露它的内存地址,因此不能返回真正的文件地址给用户

  • 内核中有一张表格,记录了所有被打开的文件对象,文件描述符就是访问这张表格的下标,文件描述符也叫做句柄,是用户操作文件的凭证

  • 内核只要启动,一定会给每个进程打开三个文件描述符,并且是一直默认打开,除非自己手动关闭

//  在<unistd.h> 定义了三个宏
​
#define STDIN_FILENO    0   //  标准输入 文件指针 stdin 
#define STDOUT_FILENO   1   //  标准输出 文件指针 stdout
#define STDERR_FILENO   2   /* 标准输入 文件指针 stderr
​
文件描述符与文件指针:
  • 在Linux系统中打开文件后,内存中(内核区间)就会有一个内核对象,也就是记录了该文件相关信息的结构变量,但是内核为了自己的安全不能把它的地址返回给用户,而且内核区间用户是无法直接访问的,就算返回也无权限访问。

  • 而且一个进程可以同时打开多份文件,所以操作系统会在内核区间创造一份索引表,表中的每一项都指向了一个打开的文件内核对象,而用户拿到的文件描述符就是该索引表的下标(主键),因此不同进程之间直接交互文件描述符是没有意义的。

  • C语言标准函数中,使用文件指针代表文件,文件指针指向的区间是进程的用户 区间的一个结构变量(文件结构变量FILE类型),里面记录了该文件的文件描述符还有一些文件缓冲区,因此某种程度上可以理解文件指针就是文件描述符,只是不同的形式,给不同的对象使用而已

int fileno(FILE *stream);
功能:把文件指针转换成文件描述符
​
FILE *fdopen(int fd, const char *mode);
功能:把文件描述符转换成文件指针

四、文件的创建与打开

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
​
int open(const char *pathname, int flags);
功能:打开文件
pathname:文件路径
flags:打开文件的方式
返回值:文件描述符
​
int open(const char *pathname, int flags, mode_t mode);
功能:打开或创建文件
pathname:文件路径
flags:打开文件的方式
mode:创建文件时的权限
返回值:成功返回文件描述符,失败返回负数
​
注意:open\creat所返回的一定是当前未使用过的最小的文件描述符
注意:一个进程可以同时打开多个文件描述符,最大的数量受limit.h中宏控制数量,在不同的系统标准中不一样,POSIX中不低于16,传统UNIX63个,现代的Linux系统255个
​
int creat(const char *pathname, mode_t mode);
功能:专门用于创建文件,但是基本不用,因为open可以覆盖它的功能
​
flags:
    O_RDONLY    只读权限
    O_WRONLY    只写权限
    O_RDWR      读写权限
    O_APPEND    打开文件后位置指针指向末尾
    O_CREAT     文件不存在会创建
    O_EXCL      如果文件存在则创建失败,如果没有该flags,文件存在直接打开
    O_TRUNC     清空内容打开
    O_ASYNC     当文件描述符可读/可写时,会向调用进程发送信号SIGIO
​
mode: 提供一个三位八进制数表示权限,当flags为O_CREAT时必须提供
     宏名     权限码
     S_IRWXU  00700  用户权限码
     S_IRUSR  00400     读权限
     S_IWUSR  00200     写权限
     S_IXUSR  00100     执行权限
     S_IRWXG  00070  同组其它用户权限
     S_IRGRP  00040 
     S_IWGRP  00020 
     S_IXGRP  00010 
     S_IRWXO  00007 除了同组的用户外,其它用户的权限
     S_IROTH  00004 
     S_IWOTH  00002 
     S_IXOTH  00001 
​
#include <unistd.h>
int close(int fd);
功能:关闭文件,成功0 失败-1
问题1:C语言可不可以定义重名函数?
可以,但是需要在不同作用域下才可以重名,同一作用域下不可以
情况1:在不同的源文件中,static声明的函数可以与其他源文件中的普通函数重名
情况2:在函数内定义的函数可以与普通函数重名
问题2: 系统调用为什么可以重名?
因为系统调用本身就不是真正的函数,而是借助了软中断实现内核执行对应的系统操作,而决定执行哪个系统调用操作是由中断编号决定的,而不是名字
问题3:标准库函数中的 r \ r+ \ w \ w+ \ a \ a+ 分别对应系统调用中的flags的哪些标志?
strace ./a.out
r       O_RDONLY
r+      O_RDWR
w       O_WRONLY|O_CREAT|O_TRUNC  0666
w+      O_RDWR|O_CREAT|O_TRUNC  0666
a       O_WRONLY|O_CREAT|O_APPEND, 0666
a+      O_RDWR|O_CREAT|O_APPEND, 0666

五、文件读写

ssize_t write(int fd, const void *buf, size_t count);
功能:写入文件内容
fd:文件描述符
buf:要写入的数据的内存首地址
count:要写入的字节数
返回值:成功写入的字节数
​
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据到内存
fd:文件描述符
buf:存储读取到的数据的内存首地址
count:想要读取的字节数,一般就是buf的大小
返回值:成功读取到的字节数
​
注意:它们与标准C的 fwrite/fread 很像,但是它们更纯粹直接

练习1:以二进制形式写入10000000个整数到文件中,分别使用标准IO和系统IO来完成,比较它们的速度谁更快,为什么?

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
​
void std_io(void)
{
    FILE* fp = fopen("test.txt","w");
    if(NULL == fp)
    {
        perror("fopen");
        return;
    }
​
    for(int i=0; i<10000000; i++)
    {
        int num = rand();
        fwrite(&num,sizeof(num),1,fp);
    }
​
    fclose(fp);
}
​
void sys_io(void)
{
    int fd = open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
    if(0 > fd)
    {
        perror("open");
        return;
    }
​
    for(int i=0; i<10000000; i++)
    {
        int num = rand();
        write(fd,&num,sizeof(num));
    }
​
    close(fd);
}
​
int main(int argc,const char* argv[])
{
    //std_io();                                                                                  
    sys_io();
}
​

六、系统IO与标准IO

  • 当系统调用被执行时,需要从用户态转换成内核态,执行完毕后又要转回用户态,如果频繁来回切换会导致性能丢失

  • 在标准IO中,内部维护一个缓冲区(1k,1024字节),要写入的数据先存储到缓冲区中,只有当满足特定条件时才会把缓冲区中数据全部通过系统调用write写入文件,从而降低了系统调用的使用频率,减少了状态的切换,因此标准IO的效率要比直接使用系统IO要快

  • 如果想要提高系统IO的速度,可以尝试自己维护一个缓冲区,先把数据存储到缓冲区,等满后再调用write,这样提高速度

  • 普通情况下建议使用标准IO,因为更快,如果对速度还有更高的要求,可以自己使用系统IO+大缓冲区

void sys_io(void)
{
    int fd = open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);                                                               
    if(0 > fd)
    {
        perror("open");
        return;
    }
​
    int buf[1024] = {};
​
    for(int i=0; i<10000000; i++)
    {
        int num = rand();
        buf[i%1024] = num;
        if(0 == (i+1)%1024)
        {
            write(fd,buf,sizeof(buf));
        }
    }
​
    close(fd);
}
​
  • UNIX/Linux只有一套读写文件的系统调用,没有像标准C中的文本读写 ,那么可以先把数据转换成字符串(sprintf/sscanf),然后再通过write\read读写,从而实现文本读写的效果

七、文件位置指针

  • 与标准IO的文件读写位置指针一样,系统IO时也会有一个表示位置的指针在移动,会随着读写操作的执行向后自动移动

  • 当需要随机位置进行读写操作时,那么需要移动位置指针的位置

off_t lseek(int fd, off_t offset, int whence);
功能:调整文件指针的位置
fd:要调整的文件描述符
offset:
    偏移值
whence:基础位置
    SEEK_SET    文件开头
    SEEK_CUR    当前位置
    SEEK_END    文件末尾
返回值:返回当前位置指针的位置 从文件开头计算该位置的字节数
​
注意:系统IO中不需要ftell函数的系统调用,因为lseek就能够完成获取位置指针位置的功能
  • 当超过文件末尾的位置再写入数据时,原末尾与最后的数据之间会有一个“数据黑洞”,但是黑洞不占用磁盘大小,但是会计算成文件的大小,不会影响后序的读写

八、文件同步

  • 大多数磁盘I/O都有缓冲区机制,写入文件其实先写入缓冲区,直到缓冲区满才将其排入写队列。降低写操作的次数,提高写操作效率,但是可能会导致磁盘文件与缓冲区数据不同步,可以借助系统调用来强制让磁盘与缓冲区的内容同步:

void sync(void);
功能:将所有被修改过的缓冲区中的数据排入写队列,立即返回,不等待写入磁盘的完成
int fsync(int fd);
功能:只针对文件fd,并且会等待写入完成才返回
int fdatasync(int fd);
功能:只同步文件fd的数据,不同步文件属性,等待写入完成才返回

九、文件描述符的状态标志

#include <unistd.h>
#include <fcntl.h>
​
int fcntl(int fd, int cmd, ... /* arg */ );
功能:设置或获取文件描述符的状态标志
cmd:
    F_GETFD     获取文件描述符状态标志
    F_SETFD     设置文件描述符状态标志
    F_GETFL     获取文件状态标志
                其中 O_CREAT\O_EXCL\O_TRUNC 获取不了
    F_SETFL     追加文件状态标志
                

十、文件锁

文件锁的意义:
  • 当多个进程同时访问同一个文件时,就有可能造成文件的数据读写混乱,为了解决这一问题可以在读写文件前给文件尝试并加锁

文件锁的限制:
  • 一般情况下,系统提供的文件锁都是劝解锁,内核主要负责文件的加锁和检查是否上锁,而不直接参与锁的控制以及协同操作,这类锁就需要程序员每次用之前都要检查是否被别的进程加锁,再实现并发操作

int fcntl(int fd, int cmd, flock* lock );
功能:对文件的某一部分进行锁操作
struct flock {
    short l_type;  /* 锁的类型
                F_RDLCK,读锁
                F_WRLCK, 写锁
                F_UNLCK 解锁*/
   short l_whence;  /* 偏移起点
                SEEK_SET, SEEK_CUR, SEEK_END */
   off_t l_start;   /* 偏移值  锁区起始位置:l_whence+l_start */
   off_t l_len;     /* 锁区长度 0表示锁到文件末尾*/
   pid_t l_pid;     /* 加锁的进程id -1表示让内核自动设置 */
   };
cmd可选:
    F_GETLK     测试lock所表示的锁能否加锁
                如果可以加则讲lock.l_type设置为F_UNLCK
                否则会通过lock.l_type返回当前锁信息
    F_SETLK     设置文件的锁定状态为lock.l_type
                成功返回0,失败返回-1,如果有其他进程持有该锁导致加锁失败,返回EACCES or EAGAIN
    F_SETLKW    设置文件的锁定状态为lock.l_type
                成功返回0,否则一直等待,除非被其它信号打断返回-1
     

进程A:读锁 进程B:读锁 可共享

进程A:写锁 进程B:读锁 互斥

进程A:读锁 进程B:写锁 互斥

进程A:写锁 进程B:写锁 互斥

十一、复制文件描述符

int dup(int oldfd);
功能:复制文件描述符
oldfd:已经打开的要复制的文件描述符
返回值:返回一个新的文件描述符,是当前可用的文件描述符中最小值,失败-1
​
int dup2(int oldfd, int newfd);
功能:复制oldfd文件描述符成指定的文件描述符newfd
如果newfd原来已经被占用,则会把它关闭重新复制
返回值:成功0 失败-1
    
注意:复制成功后,相当于两个文件描述符对应同一个打开的文件
复制文件描述符的意义:
  • 复制成功后,相当于两个文件描述符对应同一个打开的文件,可以以此实现很多奇特的操作,例如重定向文件读写、重定向命令 ls >> file

  • 通过把一个已经打开了的文件描述符fd,通过dup2重定向为标准输入0或者标准输出1,此时就会先把0、1文件关闭,然后0\1指向我们刚刚的文件fd,此后,通过输出语句执行时,相当于把本来要输出到屏幕的内容,直接写如到文件fd中,相当于执行write,如果执行输入语句,相当于把本来要从键盘中输入的内容,直接从文件fd中读取,相当于执行了read操作

十二、获取文件属性

int stat(const char *pathname, struct stat *buf);
功能:根据文件路径获取该文件的属性
int fstat(int fd, struct stat *buf);
功能:根据文件描述符获取该文件的属性
int lstat(const char *pathname, struct stat *buf);
功能:根据文件路径获取链接文件的属性
​
struct stat {
       dev_t  st_dev;         /* 文件的设备ID*/
       ino_t  st_ino;         /* 文件的inode节点号*/
       mode_t st_mode;        /* 文件的类型和权限 */
       nlink_t st_nlink;      /* 硬链接数量 */
       uid_t   st_uid;       /* 属主ID */
       gid_t     st_gid;     /* 属组ID */
       dev_t     st_rdev;    /* 特殊设备ID */
       off_t     st_size;    /* 文件的总字节数 */
       blksize_t st_blksize; /* 文件的IO块数量*/
       blkcnt_t  st_blocks;  /* 以512字节为一块,该文件占了几块*/
       struct timespec st_atim;  /* 最后访问时间*/
       struct timespec st_mtim;  /* 最后内容修改时间*/
       struct timespec st_ctim;  /* 最后文件状态属性修改时间*/
       
            #define st_atime st_atim.tv_sec 时间换算成总秒数     
           #define st_mtime st_mtim.tv_sec
           #define st_ctime st_ctim.tv_sec
       };
​
st_mode 记录了文件类型和权限
       S_IFMT     0170000   获取文件类型的掩码
       S_IFSOCK   0140000   socket文件    
       S_IFLNK    0120000   软链接文件
       S_IFREG    0100000   普通文件
       S_IFBLK    0060000   块设备文件
       S_IFDIR    0040000   目录
       S_IFCHR    0020000   字符设备文件
       S_IFIFO    0010000   FIFO    管道文件
      /*
    stat(pathname, &sb);
    if ((sb.st_mode & S_IFMT) == S_IFREG) {
               /* Handle regular file */}*/
或者借助提供的宏函数来判断文件类型:
    S_ISREG(m)  是否是普通文件
    S_ISDIR(m)  目录
    S_ISCHR(m)  字符设备文件
    S_ISBLK(m)  块设备文件
    S_ISFIFO(m) 管道文件
    S_ISLNK(m)  软链接文件
    S_ISSOCK(m) socket文件
    
判断权限:
   S_IRWXU     00700   判断属主的读写执行权限的权限码
   S_IRUSR     00400   owner has read permission
   S_IWUSR     00200   owner has write permission
   S_IXUSR     00100   owner has execute permission
​
   S_IRWXG     00070   判断属组其他用户的读写执行权限的权限码
   S_IRGRP     00040   group has read permission
   S_IWGRP     00020   group has write permission
   S_IXGRP     00010   group has execute permission
​
   S_IRWXO     00007   判断其它用户的读写执行权限的权限码
   S_IROTH     00004   others have read permission
   S_IWOTH     00002   others have write permission
   S_IXOTH     00001   others have execute permission
​
​
#include <stdio.h>                                                                                                                                                                                      
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
​
//  显示文件类型和权限
const char* mtos(mode_t m)
{
    static char s[11];
​
    if(S_ISREG(m))
        strcpy(s,"-");
    else if(S_ISDIR(m))
        strcpy(s,"d");
    else if(S_ISCHR(m))
        strcpy(s,"c");
    else if(S_ISBLK(m))
        strcpy(s,"b");
    else if(S_ISFIFO(m))
        strcpy(s,"p");
    else if(S_ISLNK(m))
        strcpy(s,"l");
    else if(S_ISSOCK(m))
        strcpy(s,"s");
​
    strcat(s,m & S_IRUSR ? "r":"-");
    strcat(s,m & S_IWUSR ? "w":"-");
    strcat(s,m & S_IXUSR ? "x":"-");
​
    strcat(s,m & S_IRGRP ? "r":"-");
    strcat(s,m & S_IWGRP ? "w":"-");
    strcat(s,m & S_IXGRP ? "x":"-");
​
    strcat(s,m & S_IROTH ? "r":"-");
    strcat(s,m & S_IWOTH ? "w":"-");
    strcat(s,m & S_IXOTH ? "x":"-");
​
    return s;
}
​
const char* ttos(time_t t)
{
    static char s[20];
    
    struct tm* time = localtime(&t);
    sprintf(s,"%04d-%02d-%02d %02d:%02d:%02d",
            time->tm_year+1900,
            time->tm_mon+1,
            time->tm_mday,
            time->tm_hour,
            time->tm_min,
            time->tm_sec);
    return s;
}
​
int main(int argc,const char* argv[])
{
    if(2 > argc)
    {
        printf("User:./a.out <path>\n");
        return 0;
    }
​
    struct stat st;
    
    if(-1 == stat(argv[1],&st))
    {
        perror("stat");
        return -1;
    }
​
    printf("设备ID:%llu\n",st.st_dev);
    printf("inode节点号:%lu\n",st.st_ino);
    printf("文件类型:%s\n",mtos(st.st_mode));
    printf("最后访问时间:%s\n",ttos(st.st_atime));
    printf("最后修改时间:%s\n",ttos(st.st_mtime));
    printf("最后状态修改时间:%s\n",ttos(st.st_ctime));
}
​

十三、文件的权限

测试文件的权限:
int access(const char *pathname, int mode);
功能:测试当前用户对该文件的权限
pathname:要测试的文件路径
mode:
    R_OK    读权限
    W_OK    写权限
    X_OK    执行权限
    F_OK    测试文件是否存在
返回值:存在权限返回0 否则返回-1
修改文件权限:
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
功能:修改文件的权限为mode,mode可以使用提供的宏,或者直接使用一个八进制数表示三组权限
注意:权限都可以修改
文件的权限屏蔽码:
  • 当使用open\creat 创建文件时,无论给什么权限创建都会成功,但是系统中记录有一个权限屏蔽码会对用户创建的文件的权限进行过滤屏蔽,最终创建出来的文件权限要除去文件屏蔽码

//  可以通过命令 umask 查看当前用户的权限屏蔽码
//  可以通过命令 umask 0xxx 修改当前终端这一次的权限屏蔽码为0xxx
​
mode_t umask(mode_t mask);
功能:给当前进程设置权限屏蔽码
mask:新的屏蔽码
返回值:旧的屏蔽码
注意:只对当前进程有效
​
注意:屏蔽码只影响open、creat ,对于chmod、fchmod 不受影响

十四、修改文件大小

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
length:想要修改成的文件字节数
成功返回0,失败返回-1
截短末尾丢弃,加长末尾添0
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
​
int main(int argc,const char* argv[])
{
    int fd = open("trunc.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
    if(-1 == fd)
    {
        perror("open");
        return -1;
    }
​
    //  要映射新文件前,需要让文件大小超过0 才能映射并输入、输出
    if(-1 == ftruncate(fd,100))
    {
        perror("ftruncate");
        close(fd);
        return -1;
    }
​
    char* str = mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_FILE,fd,0);
    if((void*)-1 == str)
    {
        perror("mmap");
        return 0;
    }
    //  往映射后的虚拟内存写入数据 
    //printf("----%s\n",str);
    sprintf(str,"hello worldxxxxxxxxxxiid\n");
    printf("----%s\n",str);
//  str[0] = 'a';   
    //  取消映射
    munmap(str,100);
​
    close(fd);
}    

十五、链接文件

Linux的文件系统会把磁盘分区成主要的两大部分
  • inode信息块

    • 默认128B,里面主要记录文件的权限、大小、所有者、修改时间等基本信息

  • block数据块

    • 默认4Kb,记录了文件名和真正的文件数据内容

  • 每个文件必须拥有一个唯一的inode以及若干个block组成,读取文件需要借助文件所在目录的block中记录的文件inode号,找到该文件的inode,inode中记录了该文件的block位置,从而最终读取文件

什么是软、硬链接文件?
硬链接文件
  • 硬链接文件没有自己inode和block,只是在不同的目录下复制了一份源文件的inode信息,可以通过该inode找到同一份源文件的block

软链接文件
  • 软链接文件会创建自己的新的inode和block,它的inode也是为了找到自己的block,而在它的block中存储的是链接源文件的文件名和inode信息

区别
  • 删除源文件,只是删除源文件的inode块,但是硬链接文件不受影响,而软链接文件就无法访问了

  • 当一个文件的硬链接数删除成0时,文件才被真正的删除

  • 修改硬链接文件内容,源文件也会被修改;而修改软链接的block,不会改变源文件的内容,反而会让软链接无法找到源文件

  • 硬链接不能链接目录,软链接可以

硬链接文件的创建和删除:
int link(const char *oldpath, const char *newpath);
功能:创建硬链接文件
与命令 link \ ln 功能一样
​
int unlink(const char *pathname);
功能:删除文件的硬链接,文件的硬链接数-1
   
int remove(const char *pathname);
功能:与unlink一致,都可以删除普通文件以及硬链接文件
注意:如果删除的文件正在被打开,则会等待文件关闭后删除
注意:如果删除的是软链接文件,则会只删除软链接文件本身,而不会对源文件有任何影响,而且没有任何一个可以借助软链接来删除链接对象文件的函数
软链接文件的创建与读取:
int symlink(const char *target, const char *linkpath);
功能:创建软链接文件
对应命令:ln -s 
​
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
功能:获取到软链接文件自身的block内容,也就是它链接对象的文件名,而不会获取到链接对象的文件内容
如果想要读取链接对象的文件内容,还是通过read\write进行

十六、工作目录

  • 工作目录指的是当前进程所在的目录,它是相对路径的起点,在操作文件时,如果没有提供文件的绝对路径信息,那么会操作工作目录下的文件,一般默认下工作目录就是当前进程的目录

char *getcwd(char *buf, size_t size);
功能:获取当前进程的工作路径,相当于命令 pwd
​
int chdir(const char *path);
int fchdir(int fd);
功能:修改工作路径,相当于cd

十七、创建、删除、读取目录

int mkdir(const char *pathname, mode_t mode);
功能:创建空白目录
mode:目录的权限,目录必须有执行权限次才能进入
​
int rmdir(const char *pathname);
功能:删除目录,只能删除空白目录
​
int chdir(const char *path);
int fchdir(int fd);
功能:修改工作路径,相当于cd
​
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
功能:打开目录文件
返回值:成功返回目录流指针,失败返回NULL
​
struct dirent *readdir(DIR *dirp);
功能:从目录流对象中读取一条条目信息
注意:读取完一条条目后,会自动的往后移动,只需要再次调用该函数,即可以读下一条目录
返回值:返回该条目录信息的结构指针或者NULL(读取失败或者读到了目录末尾结束)
结构体 struct dirent里面存储了目录中某个文件的信息
struct dirent {
   ino_t          d_ino;       /* inode节点号*/
   off_t          d_off;       /* 下一条条目的偏移量 注意是磁盘偏移量,而非内存地址*/
   unsigned short d_reclen;    /* 当前条目的长度 */
   unsigned char  d_type;      /* 文件类型 */
   char           d_name[256]; /* 文件名 */
};
d_type的取值:
    DT_BLK  块设备文件
    DT_CHR  字符设备文件
    DT_DIR  目录
    DT_FIFO 管道文件
    DT_LNK  软链接文件
    DT_REG  普通文件
    DT_SOCK socket文件
    DT_UNKNOWN  未知
​
void rewinddir(DIR *dirp);
功能:复位目录流,设置目录流的位置指针回到开头
​
long telldir(DIR *dirp);
功能:获取当前目录流的位置
​
void seekdir(DIR *dirp, long loc);
功能:设置目录流的读取位置,这样可以进行任意条目的获取
​
int closedir(DIR *dirp);
功能:关闭目录文件
本项目是一个基于SSM(Spring+SpringMVC+MyBatis)后端框架与Vue.js前端框架开发的疫情居家办公系统。该系统旨在为居家办公的员工提供一个高效、便捷的工作环境,同时帮助企业更好地管理远程工作流程。项目包含了完整的数据库设计、前后端代码实现以及详细的文档说明,非常适合计算机相关专业的毕设学生和需要进行项目实战练习的Java学习者。 系统的核心功能包括用户管理、任务分配、进度跟踪、文件共享和在线沟通等。用户管理模块允许管理员创建和管理用户账户,分配不同的权限。任务分配模块使项目经理能够轻松地分配任务给团队成员,并设置截止日期。进度跟踪模块允许员工实时更新他们的工作状态,确保项目按计划进行。文件共享模块提供了一个安全的平台,让团队成员可以共享和协作处理文档。在线沟通模块则支持即时消息和视频会议,以增强团队之间的沟通效率。 技术栈方面,后端采用了Spring框架来管理业务逻辑,SpringMVC用于构建Web应用程序,MyBatis作为ORM框架简化数据库操作。前端则使用Vue.js来实现动态用户界面,搭配Vue Router进行页面导航,以及Vuex进行状态管理。数据库选用MySQL,确保数据的安全性和可靠性。 该项目不仅提供了一个完整的技术实现示例,还为开发者留下了扩展和改进的空间,可以根据实际需求添加新功能或优化现有功能。
本项目是一个基于SSM(Spring+SpringMVC+MyBatis)后端框架与Vue.js前端框架开发的网上球鞋竞拍系统。该项目旨在为球鞋爱好者提供一个便捷、高效的在线竞拍平台,用户可以在此平台上浏览、搜索、竞拍心仪的球鞋,并参与到各种有趣的竞拍活动中。 系统的主要功能包括用户注册登录、球鞋信息展示、竞拍活动创建与管理、实时竞拍以及交易安全保障等。用户可以通过注册账号后,浏览平台上发布的各类球鞋信息,包括品牌、型号、颜色、尺码以及当前竞拍状态等。系统支持用户创建和管理自己的竞拍活动,设定竞拍规则和时间,同时提供实时竞拍功能,确保公平、透明的交易过程。 在技术实现上,后端采用SSM框架进行开发,Spring负责业务逻辑层,SpringMVC处理Web请求,MyBatis进行数据库操作,保证了系统的稳定性和扩展性。前端则使用Vue.js框架,结合Axios进行数据请求,实现了前后端分离,提高了开发效率和用户体验。 数据库设计方面,系统采用了MySQL数据库,存储用户信息、球鞋信息、竞拍活动等数据,确保数据的安全性和完整性。此外,项目还包含了详细的文档资料,包括需求分析、系统设计、数据库设计以及测试报告等,为项目的实施和维护提供了有力的支持。 该项目不仅适合作为计算机相关专业学生的毕业设计题目,也适合Java学习者进行实战练习,通过在此基础上进行功能扩展和改进,可以进一步提升编程技能和项目管理能力。
【使用教程】 一、环境配置 1、建议下载anaconda和pycharm 在anaconda中配置好环境,然后直接导入到pycharm中,在pycharm中运行项目 anaconda和pycharm安装及环境配置参考网上博客,有很多博主介绍 2、在anacodna中安装requirements.txt中的软件包 命令为:pip install -r requirements.txt 或者改成清华源后再执行以上命令,这样安装要快一些 软件包都安装成功后才算成功 3、安装好软件包后,把anaconda中对应的python导入到pycharm中即可(不难,参考网上博客) 二、环境配置好后,开始训练(也可以训练自己数据集) 1、数据集准备 需要准备yolo格式的目标检测数据集,如果不清楚yolo数据集格式,或者有其他数据训练需求,请看博主yolo格式各种数据集集合链接:https://blog.csdn.net/DeepLearning_/article/details/127276492 里面涵盖了上百种yolo数据集,且在不断更新,基本都是实际项目使用。来自于网上收集、实际场景采集制作等,自己使用labelimg标注工具标注的。数据集质量绝对有保证! 本项目所使用的数据集,见csdn该资源下载页面中的介绍栏,里面有对应的下载链接,下载后可直接使用。 2、数据准备好,开始修改配置文件 参考代码中data文件夹下的banana_ripe.yaml,可以自己新建一个不同名称的yaml文件 train:训练集的图片路径 val:验证集的图片路径 names: 0: very-ripe 类别1 1: immature 类别2 2: mid-ripe 类别3 格式按照banana_ripe.yaml照葫芦画瓢就行,不需要过多参考网上的 3、修改train_dual.py中的配置参数,开始训练模型 方式一: 修改点: a.--weights参数,填入'yolov9-s.pt',博主训练的是yolov9-s,根据自己需求可自定义 b.--cfg参数,填入 models/detect/yolov9-c.yaml c.--data参数,填入data/banana_ripe.yaml,可自定义自己的yaml路径 d.--hyp参数,填入hyp.scratch-high.yaml e.--epochs参数,填入100或者200都行,根据自己的数据集可改 f.--batch-size参数,根据自己的电脑性能(显存大小)自定义修改 g.--device参数,一张显卡的话,就填0。没显卡,使用cpu训练,就填cpu h.--close-mosaic参数,填入15 以上修改好,直接pycharm中运行train_dual.py开始训练 方式二: 命令行方式,在pycharm中的终端窗口输入如下命令,可根据自己情况修改参数 官方示例:python train_dual.py --workers 8 --device 0 --batch 16 --data data/coco.yaml --img 640 --cfg models/detect/yolov9-c.yaml --weights '' --name yolov9-c --hyp hyp.scratch-high.yaml --min-items 0 --epochs 500 --close-mosaic 15 训练完会在runs/train文件下生成对应的训练文件及模型,后续测试可以拿来用。 三、测试 1、训练完,测试 修改detect_dual.py中的参数 --weights,改成上面训练得到的best.pt对应的路径 --source,需要测试的数据图片存放的位置,代码中的test_imgs --conf-thres,置信度阈值,自定义修改 --iou-thres,iou阈值,自定义修改 其他默认即可 pycharm中运行detect_dual.py 在runs/detect文件夹下存放检测结果图片或者视频 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值