文件与目录

目录

一、文件IO

系统调用

open函数、close函数

read函数、write函数

perror函数(打印异常)

系统调用和库函数比较—预读入缓输出

fputc/fgetc实现cp

read/write实现cp

文件描述符

阻塞和非阻塞

fcntl改文件属性

lseek函数

truncate函数

二、文件系统

传入传出参数

目录项和inode

文件操作

stat函数!!!

lstat查看文件类型

link和unlink函数

文件目录rwx权限差异

目录操作函数

实现ls

递归遍历目录!!!

dup和dup2函数

fcntl实现dup描述符

一、文件IO

系统调用

操作系统实现并提供给外部应用程序的编程接口。是应用程序同系统之间数据交互的桥梁。与库函数的不同之处,库函数是程序库中的函数

open函数、close函数

#include <unistd.h>:包含man里面系统头文件
#include <fcntl.h>:包含flags宏定义
#include <errno.h>

/*参数:
	pathname: 欲打开的文件路径名
	flags:文件打开方式:	#include <fcntl.h>
返回值:
     成功: 打开文件所得到对应的 文件描述符(整数)
	 失败: -1,设置errno,需引入头文件 errno.h
*/
int open(char *pathname, int flags)

/*
参数:
	pathname: 欲打开的文件路径名
	flags:文件打开方式:	
     O_RDONLY|O_WRONLY|O_RDWR	
     O_CREAT|O_APPEND|O_TRUNC|O_EXCL|O_NONBLOCK ....
	mode: 参数3使用的前提, 参2指定了 O_CREAT。	
    取值8进制数,用来描述文件的 访问权限。 rwx(r:4,w:2,x:1)    0664
		  创建文件最终权限 = mode & ~umask
返回值:
	成功: 打开文件所得到对应的 文件描述符(整数)
	失败: -1, 设置errno
*/
int open(char *pathname, int flags, mode_t mode)

//关闭文件描述符
int close(int fd);

//strerror函数:输出错误提示语,传入errno
char *strerror(int errnum);

read函数、write函数

#include <unistd.h>
/*
参数:
	fd:文件描述符
	buf:存数据的缓冲区
	count:缓冲区大小
返回值:
	0:		读到文件末尾。
	成功;	> 0 读到的字节数。
	失败:	-1, 设置 errno
	-1: 	并且 errno = EAGIN 或 EWOULDBLOCK, 说明不是read失败,而是read在以非阻塞
            方式读一个设备文件(网络文件),并且文件无数据。
*/
ssize_t read(int fd, void *buf, size_t count);

#include <unistd.h>
/*
参数:
	fd:文件描述符
	buf:待写出数据的缓冲区
	count:数据大小
返回值:
	成功:写入的字节数。
	失败:-1, 设置 errno
*/
ssize_t write(int fd, const void *buf, size_t count);

perror函数(打印异常)

 #include <stdio.h>

void perror(const char *s);

系统调用和库函数比较—预读入缓输出

fputc/fgetc实现cp

read/write实现cp

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

int main(int argc , char* argv[]){
    char buf[1024];

    int n = 0;

    int fd1 = open(argv[1], O_RDONLY);
    int fd2 = open(argv[2], O_RDWR|O_CREAT|O_TRUNC,0664);

    while((n = read(fd1,buf,1024)) != 0){
        write(fd2, buf,n);
    }

    close(fd1);
    close(fd2);

    return 0;
}

实验结果:read/write速度慢
原因分析:
<1> 
read/write这块,每次写一个字节,会疯狂进行内核态和用户态的切换,所以非常耗时。
<2> fgetc/fputc,有个缓冲区,4096,所以它并不是一个字节一个字节地写,内核和用户切换就比较少预读入,缓输出机制。
<3> 标准IO函数自带用户缓冲区,系统调用无用户级缓冲。系统缓冲区是都有的。

文件描述符

文件描述符是指向一个文件结构体的指针。PCB进程控制块:本质 结构体
成员:文件描述符表。
文件描述符:0/1/2/3/4。。。。/1023 表中可用的最小的。
0 -STDIN_FILENO 键盘
1 - STDOUT_FILENO 显示器
2 - STDERR_FILENO

阻塞和非阻塞

产生阻塞的场景:读设备文件。读网络文件的属性。(读常规文件无阻塞概念。)
/dev/tty -- 终端文件(属于设备文件)。
open("/dev/tty", O_RDWR | O_NONBLOCK) --- 设置 /dev/tty 非阻塞状态。(默认为阻塞状态)
更改非阻塞读取终端——超时设置

#include <unistd.h>  
#include <fcntl.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <errno.h>  
#include <string.h>  
  
#define MSG_TRY "try again\n"  
#define MSG_TIMEOUT "time out\n"  
  
int main(void)  
{  
    //打开文件
    int fd, n, i;  
    fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);  
    if(fd < 0){  
        perror("open /dev/tty");  
        exit(1);  
    }  
    printf("open /dev/tty ok... %d\n", fd);  
  	
    //轮询读取
    char buf[10];  
    for (i = 0; i < 5; i++){  
        n = read(fd, buf, 10);  
        if (n > 0) {                    //说明读到了东西  
            break;  
        }  
        if (errno != EAGAIN) {          //EWOULDBLOCK    
            perror("read /dev/tty");  
            exit(1);  
        } else {  
            write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));  
            sleep(2);  
        }  
    }  
  	//超时判断
    if (i == 5) {  
        write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));  
    } else {  
        write(STDOUT_FILENO, buf, n);  
    }  
  
    //关闭文件
    close(fd);  
    return 0;  
}  

fcntl改文件属性

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

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

/*
参数:
fd		文件描述符
cmd		命令,决定了后续参数个数
获取文件状态: F_GETFL
设置文件状态: F_SETFL
返回值:
int flgs = fcntl(fd,  F_GETFL);
flgs |= O_NONBLOCK
fcntl(fd,  F_SETFL, flgs);
*/

int fcntl(int fd, int cmd, ... /* arg */ );

终端文件默认是阻塞读的,这里用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;  
}

lseek函数

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

/*
参数:
	fd:文件描述符
	offset: 偏移量,就是将读写指针从whence指定位置向后偏移offset个单位
	whence:起始偏移位置 
SEEK_SET / SEEK_CUR / SEEK_END
返回值:
	成功:较起始位置偏移量
	失败:-1 errno
*/

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

lseek示例,写一个句子到空白文件,完事调整光标位置,读取刚才写那个文件。
这个示例中,如果不调整光标位置,是读取不到内容的,因为读写指针在内容的末尾

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <string.h>  
#include <fcntl.h>  
  
int main(void)  
{  
    int fd, n;  
    char msg[] = "It's a test for lseek\n";  
    char ch;  
  
    fd = open("lseek.txt", O_RDWR|O_CREAT, 0644);  
    if(fd < 0){  
        perror("open lseek.txt error");  
        exit(1);  
    }  
  
    write(fd, msg, strlen(msg));    //使用fd对打开的文件进行写操作,问价读写位置位于文件结尾处。  
  
    lseek(fd, 0, SEEK_SET);         //修改文件读写指针位置,位于文件开头。 注释该行会怎样呢?  
  
    while((n = read(fd, &ch, 1))){  
        if(n < 0){  
            perror("read error");  
            exit(1);  
        }  
        write(STDOUT_FILENO, &ch, n);   //将文件内容按字节读出,写出到屏幕  
    }  
  
    close(fd);  
  
    return 0;  
}  

应用场景:

1. 文件的“读”、“写”使用同一偏移位置。
2. 使用lseek获取文件大小(返回值接收)
    lseek(fd , 0, SEEK_END);
3. 使用lseek拓展文件大小:要想使文件大小真正拓展,必须【引起IO操作】

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

int main(int argc,char*argv[]){
    int fd = opne(argv[1], O_RDWR);
    if(fd == -1){
       perror("open error");
       exit(1);
    }

    int lenth = lsek(fd, 101, SEEK_END);
    printf("file size: %d\n", lenth);

    write(fd, "$", 1);

    close(fd);
    return 0;
}

简单小结一下:
1、对于写文件再读取那个例子,由于文件写完之后未关闭,读写指针在文件末尾,所以不调节指针,直接读取不到内容。
2、lseek读取的文件大小总是相对文件头部而言。
3、用lseek读取文件大小实际用的是读写指针初末位置的偏移差,一个新开文件,读写指针初位置都在文件开头。如果用这个来扩展文件大小,必须引起IO才行,于是就至少要写入一个字符。上面代码出现lseek返回799,ls查看为800的原因是,lseek读取到偏移差的时候,还没有写入最后的‘$’符号,末尾那一大堆^@,是文件空洞,如果自己写进去的也想保持队形,就写入“\0”。

truncate函数

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

//直接拓展文件大小
int truncate(const char *path, off_t length);

例:int ret = truncate("dict.cp", 250);

二、文件系统

传入传出参数

传入参数:
1. 指针作为函数参数。
2. 同常有const关键字修饰。
3. 指针指向有效区域, 在函数内部做读操作。

传出参数:
1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间可以无意义,但必须有效。
3. 在函数内部,做写操作。
4。函数调用结束后,充当函数返回值。

传入传出参数:

1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间有实际意义。
3. 在函数内部,先做读操作,后做写操作。
4. 函数调用结束后,充当函数返回值。

目录项和inode

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

inode本质是结构体,存储文件的属性信息,如:权限、类型、大小、时间、用户、盘快位置…
也叫做文件属性管理结构,大多数的inode都存储在磁盘上。具体可通过man 7 inode
所谓的删除文件,就是删除inode,但是数据其实还是在硬盘上,以后会覆盖掉。

  dentry 目录项,其本质依然是结构体 重要成员变量有两个 {文件名,inode ,...},而文件内
容(data)保存在磁盘盘块中。 

文件操作

stat函数!!!

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

/*
参数:
	path: 文件路径
	buf:(传出参数) 存放文件属性,inode结构体指针。
返回值:
	成功: 0
	失败: -1 errno
*/
//会存在符号穿透
int stat(const char *pathname, struct stat *statbuf);
//不会存在符号穿透
int lstat(const char *pathname, struct stat *statbuf);


struct stat {
    dev_t     st_dev;         /* ID of device containing file */
    ino_t     st_ino;         /* Inode number */
    mode_t    st_mode;        /* File type and mode */
    nlink_t   st_nlink;       /* Number of hard links */
    uid_t     st_uid;         /* User ID of owner */
    gid_t     st_gid;         /* Group ID of owner */
    dev_t     st_rdev;        /* Device ID (if special file) */
    off_t     st_size;        /* Total size, in bytes */
    blksize_t st_blksize;     /* Block size for filesystem I/O */
    blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */

    /* Since Linux 2.6, the kernel supports nanosecond
    precision for the following timestamp fields.
    For the details before Linux 2.6, see NOTES. */

    struct timespec st_atim;  /* Time of last access */
    struct timespec st_mtim;  /* Time of last modification */
    struct timespec st_ctim;  /* Time of last status change */

    #define st_atime st_atim.tv_sec      /* Backward compatibility */
    #define st_mtime st_mtim.tv_sec
    #define st_ctime st_ctim.tv_sec
 };

获取文件大小: buf.st_size
获取文件类型: buf.st_mode
获取文件权限: buf.st_mode

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#icnlude <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;
}

lstat查看文件类型



int main(int argc,char *argv[]){
    struct stat sb;
    
    int ret = lstat(argv[1],&sb);
    
    if(S_ISREG(sb.st_mode)){
        printf("It is a regular\n");
    }else if(S_ISDIR(sb.st_mode)){
        printf("It is a dir\n");
    }else if(S_ISFIFO(sb.st_mode)){
        printf("It is a pipe\n");
    }else if(S_ISLNK(sb.st_mode)){
        printf("It is a sym link\n"); //无法显示符号连接
    } //stat存在符号穿透 显示的是符号连接的文件

    return 0;
}

link和unlink函数

硬链接数就是dentry数目
link就是用来创建硬链接的
link可以用来实现mv命令

#include <unistd.h>

int link(const char *oldpath, const char *newpath);
用这个来实现mv或者rename,用oldpath来创建newpath,完事儿删除oldpath就行。

int unlink(const char *pathname);
unlink是删除一个文件的目录项dentry,使【硬链接数-1】
unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,
但该文件仍不会马上被释放,要等到所有打开文件的进程关闭该文件,系统才会挑
时间将该文件释放掉。
/* 
 *unlink函数是删除一个dentry 
 */  
#include <unistd.h>  
#include <fcntl.h>  
#include <stdlib.h>  
#include <string.h>  
#include <stdio.h>  
  
  
int main(void) {
    int fd, ret;  
    char *p = "test of unlink\n";  
    char *p2 = "after write something.\n";  
  
    fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);  
    if(fd < 0){  
        perror("open temp error");  
        exit(1);  
    }  
  
    ret = unlink("temp.txt");        //具备了被释放的条件  
    if(ret < 0){  
        perror("unlink error");  
        exit(1);  
    }  
  
    ret = write(fd, p, strlen(p));  
    if (ret == -1) {  
        perror("-----write error");  
    }  
  
    printf("hi! I'm printf\n");  
    ret = write(fd, p2, strlen(p2));  
    if (ret == -1) {  
        perror("-----write error");  
    }  
  
    printf("Enter anykey continue\n");  
    getchar();  
  
    close(fd);  
  
    return 0;  
}  

隐式回收:
当进程结束运行时,所有进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。

文件目录rwx权限差异

目录操作函数

//成功返回指向该目录结构体指针,失败返回 NULL
DIR * opendir(char *name);

//成功:0;失败:-1 设置 errno 为相应值
int closedir(DIR *dp);

/*
成功返回目录项结构体指针;失败返回NULL设置errno为相应值
需注意返回值,读取数据结束时也返回 NULL 值,所以应借助 errno 进一步加以区分。
*/
struct dirent *readdir(DIR * dp);


struct dirent {
     ino_t          d_ino;       /* Inode number */
     off_t          d_off;       /* Not an offset; see below */
     unsigned short d_reclen;    /* Length of this record */
     unsigned char  d_type;      /* Type of file; not supported
                                              by all filesystem types */
     char           d_name[256]; /* Null-terminated filename */
};

实现ls

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


int main (int argc, char*argv[]){

    DIR* sdir;
    struct dirent* sd;

    sdir = opendir(argv[1]);


    while((sd = readdir(sdir))!=NULL){
        /*
            strcmp比较两个字符串的大小,一个字符一个字符比较,按ASCLL码比较
            标准规定:
            第一个字符串大于第二个字符串,则返回大于0的数字
            第一个字符串等于第二个字符串,则返回0
            第一个字符串小于第二个字符串,则返回小于0的数字
        */
        if(strcmp(sd->d_name,".")==0||strcmp(sd->d_name,"..")==0)
            continue;
        printf("%s\t",sd->d_name);
    };

    printf("\n");

    closedir(sdir);
}

递归遍历目录!!!

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

void isFile(char *name);

void read_dir(char* dir){

     char path[256];
     DIR* dp;
     struct dirent *sdp;

     dp = opendir(dir);
     if(dp == NULL){
        perror("opendir error");
        return;
     }

     while((sdp = readdir(dp)) != NULL){
         if(strcmp(sdp->d_name,".")==0||strcmp(sdp->d_name,"..")==0){
                continue;
         }
         sprintf(path,"%s/%s", dir, sdp->d_name);
         isFile(path);
     }
     closedir(dp);
}

void isFile(char *name){
     int ret = 0;
     struct stat sb;

     ret = stat(name, &sb);
     if(ret == -1){
        perror("stat error");
        return ;
     }

     if(S_ISDIR(sb.st_mode)){
         read_dir(name);
     }
     printf("%s\t%ld\n",name,sb.st_size);

     return;
}


int main(int argc, char* argv[]){

   if(argc == 1){
        isFile(".");
   }else{
        while(--argc > 0)              //可一次查询多个目录
                isFile(*++argv);       //循环调用该函数处理各个目录
   }
   return 0;
}

dup和dup2函数

#include <unistd.h>

//文件描述符复制
int dup(int oldfd);
oldfd: 已有文件描述符
返回:新文件描述符,这个描述符和oldfd指向相同内容。

//文件描述符复制,oldfd拷贝给newfd。返回newfd ,但newfd全指向oldfd指向的结构体
int dup2(int oldfd, int newfd);
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

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

    int fdret = dup2(fd1,fd2);
    
    write(fd2,"1234567", 7);

    dup2(fd1,STDOUT_FILENO);
    
    return 0;
}

注意:打开一个文件,读写指针默认在文件头,如果文件本身有内容,直接写入会覆盖原有内容。

fcntl实现dup描述符

int fcntl(int fd, int cmd, ....);
cmd: F_DUPFD
参3:  	被占用的,返回最小可用的。
		未被占用的, 返回=该值的文件描述符。
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

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

    int newfd = fcntl(fd1,F_DUPFD,0);
    
    write(newfd,"1234567", 7);    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值