简明Linux-Linux下的常用文件操作(1)

1open/close函数

查看系统调用的命令:

man 2 functionName

主要查看函数名称,调用方法,描述,参数,返回值等等,先学习如何使用,在学习内部原理

文件描述符是一个整数,会一直伴随着linux各种系统调用

函数名称和功能:open 打开或者创建一个文件

头文件:

#include <sys/types.h>

#include <sys/stat.h>

(上面两个头文件可以用#include <unistd.h>代替)

#include <fcntl.h>

函数原型:

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

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

参数:

*char pathname:**代表文件的目录;

flags: O_RDONLY 只读 O_WRONLY 只写 O_RDWR 可读可写

​ O_APPEND 追加 O_CREAT 创建新文件 O_EXCL 判断文件是否存在 O_TRUNC 将文件阶段为0,清空 O_NONBLOCK 非阻塞

头文件位于 #include <fcntl.h>中

创建文件时,指定文件访问权限。权限同时受到umask影响。结论为(umask默认创建文件权限的反码):

文件权限=mode&~umask

返回值:返回一个int,文件描述符,返回-1代表操作失败

#include <errno.h> errno 操作系统的全局变量???

#include <string.h> char* strerror(int errnum) 打印错误的具体信息

mode:

使用的前提是,参数2指定了O_CREAT。参数3取值取8进制数,用来描述文件的访问权限,rwx, 0664

最终创建得到的文件权限=mode&(~umask)

open函数常见错误:

1)打开文件不存在;

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, char** argv)
{
    int fd;
    fd=open("./dict.cd",O_RDONLY);
    printf("fd=%d\n",fd);
    printf("errno=%d\n",errno);
    printf("%s\n",strerror(errno));
    close(fd);
    return 0;
}
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ ./open
fd=-1
errno=2
No such file or directory

2)以写方式打开只读文件(没有对应的权限);

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, char** argv)
{
    int fd;
    fd=open("./m6.txt",O_WRONLY);
    printf("fd=%d\n",fd);
    printf("errno=%d\n",errno);
    printf("%s\n",strerror(errno));
    close(fd);
    return 0;
}
#修改m6.txt文件的属性为只读
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ chmod 0444 m6.txt 
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ ls
dict.CP  dict.txt  m6.txt  open  open.c
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ ls -l
总用量 24
-rw-r--r-- 1 daniel daniel     0 510 04:24 dict.CP
-rw-rw-r-- 1 daniel daniel     0 510 04:14 dict.txt
-r--r--r-- 1 daniel daniel     0 510 05:39 m6.txt
-rwxrwxr-x 1 daniel daniel 16928 510 04:44 open
-rw-rw-r-- 1 daniel daniel   302 510 05:38 open.c
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ gcc open.c -o open2
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ ./open2
fd=-1
errno=13
Permission denied

3)以只写方式打开目录(open只能打开普通文件);

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, char** argv)
{
    int fd;
    fd=open("./m5",O_WRONLY);
    printf("fd=%d\n",fd);
    printf("errno=%d\n",errno);
    printf("%s\n",strerror(errno));
    close(fd);
    return 0;
}
#m5是一个目录文件
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ mkdir m5
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ gcc open.c -o open2
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ ./open2
fd=-1
errno=21
Is a directory

close函数原型:

int close(int fd)

功能:关闭文件描述符

参数:

fd 调用open函数得到的文件描述符;

返回值:

0代表成功 -1代表失败

错误处理函数:

与errno相关。

//打印错误的编号
printf("errno=%d\n",errno);
//打印错误的具体内容
printf("xxx error:%s\n",strerror(int errno));

void perror(const char *)

是errno和strerror的结合;头文件为#include<stdio.h>

2 read/write文件读写操作

*函数:ssize_t read(int fd, void buf, size_t count)

头文件:#include <unistd.h>

参数:fd 文件描述符 buf 缓冲区指针 count 需要读入的字节数

功能:从fd的文件描述符中读取指定count大小的字节,并且写入缓冲区,buf指针当中;当函数返回0,代表读到了文件结尾,当函数返回-1,代表发生错误;当count=0,或者提示错误,或者返回值为0;

返回值:成功,返回读入的字节数,错误,返回-1;0代表读到文件结尾;

当返回-1,且erron=EAGIN/EVOULDBLOCK时,说明不是read失败,而是以非阻塞的形式读取设备文件,而且设备没有输入

*函数:ssize_t write(int fd, const void buf, size_t count)

头文件:#include <unistd.h>

参数:fd 文件描述符 buf 待写出数据的缓冲区 count 数据大小

功能:从fd的文件描述符中读取指定count大小的字节,并且写入缓冲区,buf指针当中;当函数返回0,代表读到了文件结尾,当函数返回-1,代表发生错误;当count=0,或者提示错误,或者返回值为0;

返回值:成功,返回写入的字节数,错误,返回-1;0代表没有数据可写了

通过调用系统函数read()和write()实现shell里面的cp命令:

#include <unistd.h>
#include <fcntl.h>
//#include <stdio.h>
//从命令行接收参数,并且初始化堆栈
int main(int argc, char *argv[])
{
    //创建一个1024字节的缓冲区
    char buf[1024];
    //用于接收读取字符的数量
    int n=0;
    //以只读的方式打开传入参数的第一个文件
    int fd1=open(argv[1],O_RDONLY);
    //以读写方式打开第二个文件,如果没有,需要创建,如果有,需要截断为0
    int fd2=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0664);
    //持续读取字符,直到返回值  n=0 标志读到文件尾部为止
    while((n=read(fd1,buf,1024))!=0){
        //将读入的字符写入文件
        write(fd2,buf,n);
    }
    //关闭文件描述符
    close(fd1);
    close(fd2);
    return 0;
}

3 系统调用和库函数的比较-缓冲区相关

使用系统调用函数,实现每次拷贝1个字符到另外的文件和使用库函数fputc//从表面看也是每次拷贝一个字符到另外的文件。比较两者的运行速度。

使用库函数实现的代码如下:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
    FILE *fp, *fp_out;
    int n;
    fp=fopen(argv[1],"r");
    if(fp==NULL){
        perror("fopen error");
        exit(1);
    }
    fp_out=fopen(argv[2],"w");
    if(fp==NULL){
        perror("fopen error");
        exit(1);
    }
    while((n=fgetc(fp))!=EOF){
        fputc(n,fp_out);
    }
    fclose(fp);
    fclose(fp_out);
    return 0;
}

使用系统调用函数实现的方法如下:

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#define N 1
//从命令行接收参数,并且初始化堆栈
int main(int argc, char *argv[])
{
    //创建一个1024字节的缓冲区
    char buf[N];
    //用于接收读取字符的数量
    int n=0;
    //以只读的方式打开传入参数的第一个文件
    int fd1=open(argv[1],O_RDONLY);
    if (fd1==-1){
        perror("open argv1 error");
        exit(1);
    }
    //以读写方式打开第二个文件,如果没有,需要创建,如果有,需要截断为0 
    int fd2=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0664);
     if (fd2==-1){
        perror("open argv2 error");
        exit(1);
    }
    //持续读取字符,直到返回值  n=0 标志读到文件尾部为止
    while((n=read(fd1,buf,N))!=0){
        //将读入的字符写入文件
        write(fd2,buf,n);
    }
    //关闭文件描述符
    close(fd1);
    close(fd2);
    return 0;
}

最后经过比较发现,方法2的耗时比方法1耗时长的多。

原因,使用strace指令可以查看程序运行时使用的系统调用

daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ strace ./myCP2.out xajh.txt 1.txt 
read(3, "\215\212\344\270\252\345\270\210\347\210\266\357\274\214\345\216\237\350\257\245\345\216\273\350\260\242\347\232\204\343\200\202"..., 4096) = 4096
write(4, " \r\n\n    \350\277\231\346\227\266\344\273\244\347\213\220\345\206\262\345\267\262\345\260\206\346\201\222"..., 4096) = 4096
read(3, "\350\265\260\345\207\272\345\261\213\345\216\273\343\200\202 \r\n\n    \347\233\210\347\233\210\345\276\252"..., 4096) = 4096
write(4, "\215\212\344\270\252\345\270\210\347\210\266\357\274\214\345\216\237\350\257\245\345\216\273\350\260\242\347\232\204\343\200\202"..., 4096) = 4096

经过查看得知,使用库函数C程序,底层也是调用了系统调用的read()和write()函数,但是每次操作的字节数是4096个字节;也就是说虽然fputc表面每次只写入1个字节,但每4096个字节调用1次读或者写函数。

而使用同样的命令查看系统调用C函数用到的系统调用,发现每次调用系统函数只读入或者写入一个字节;这便导致了运行速度的差异;

read(3, "\243", 1)                      = 1
write(4, "\243", 1)                     = 1
read(3, "\350", 1)                      = 1
write(4, "\350", 1)                     = 1
read(3, "\243", 1)                      = 1
write(4, "\243", 1)                     = 1
read(3, "\263", 1)                      = 1
write(4, "\263", 1)                     = 1

fopen>>>>>>4096字节>>>>>>4096字节>>>>>>4096字节>>>>>>>BINGO!

用户区 标准库函数 内核区 缓冲区 硬件(硬盘区)

open>>>>>>>>>>>>>>>>>>>>>1字节>>>>>>>>4096字节>>>>>>>BINGO!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mAecj6Wk-1642598841072)(/home/daniel/.config/Typora/typora-user-images/image-20210512224341331.png)]

使用库函数,将每次读写字节的缓冲区定义为1字节时,使得用户区和内核区交互过于频繁,而这个过程时相当耗时的。如果将read()和write()函数的字节数,修改回4096,两个程序的运行时间,应该是使用系统调用更快。

read和write函数被称为 Unbuffered I/O,指的是无用户缓冲区,但是不保证不使用内核缓冲区

预读入,缓输出机制

即使学了系统调用,能使用库函数的时候,还是尽量使用库函数。

因为库函数久经考验,效率相对较高,而对于系统调用来说,如果对于原理不深刻了解,可能会出现效率低下问题。

4 文件描述符

PCB 进程控制块 本质是一个结构体 与之相对的是TCB 线程控制块

struct task_struct{ 结构体成员 } 其中有个文件描述符表

成员中有一个指针,指针指向一个文件描述符表

文件描述符表,实际上是一个key-value表,键值对表,key是一个正整数,也就是我们见到的文件描述符,而value是一个指针,指向一个文件结构体,内部含有文件的各种信息;

主要包含文件描述符,文件读写位置和IO缓冲区3部分内容

struct file {

​ …

​ 文件偏移量

​ 文件访问权限

​ 文件的打开标志

​ 文件内核缓冲区的首地址

​ struct operations *f_op;

​ } 结构体中包含文件的描述信息;

查看方法:

(1)/usr/src/linux-headers-3.16.0-30/include/linux/fs.h

0 STDIN_FILENO 标准输入

1 STDOUT_FILENO 标准输出

2 STDERR_FILENO 标准错误

3

1023 …

单个进程最多打开1024个文件,其中0,1,2已经被标准输入,标准输出和标准错误所占用,只能使用剩余编号的最小值。

文件描述符相当于句柄。我们在程序中通过文件描述符,对对应的文件进行操作。

使用 ulimit -a 查看

5 阻塞和非阻塞

阻塞/非阻塞:

​ 产生阻塞的场景:读设备文件和读网络文件(读常规文件没有阻塞概念)

​ /dev/tty --终端文件

读常规的文件是不会产生堵塞的,无论读多少字节,read一定会在有限的时间内返回。

1个产生堵塞的示例:

//block.c>>>>block.out
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
    int fd1;
    char* buf[20];
    int n=20;
    //从标准输入中读入n个字符,键盘的输入储存在标准输入中
    //即标准输入是一个由外部设备给定的输入
    read(STDIN_FILENO,buf,n);
    //将读到的字符写入标准输出中,标准输出是终端显示
    write(STDOUT_FILENO,buf,n);
    return 0;
}

执行程序之后,终端会一直等待用户输入字符,这便是一种阻塞。

当read函数,当返回-1,且erron=EAGIN/EVOULDBLOCK时,说明不是read失败,而是以非阻塞的形式读取设备文件,而且设备没有输入

阻塞和非阻塞不是read和write函数的特性,而是设备文件或者网络文件的属性;

//block2.c
//以非阻塞形式打开设备文件
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
    int fd;
    char buf[10];
    int n;
    //以只读和非阻塞方式打开设备输入文件
    //原来设备文件是阻塞性质的,现在修改为非阻塞性质的
    fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
    if (fd < 0)
    {
        perror("open /dev/tty");
        exit(1);
    }
    while (1)
    {
        n = read(fd, buf, 10);
        if (n < 0)
        {
            if (errno != EAGAIN){
                perror("read /dev/tty");
                exit(1);
            }
            //n=-1,errno=EAGAIN,以非阻塞形式读数据,但是设备没有输入
            //每隔2s重新读取一次数据
            else{
                write(STDOUT_FILENO, "try agin\n", strlen("try again\n"));
                sleep(2);
                continue;
            }
        }
        else{
            //读取数据成功,将数据写到终端,关闭文件描述符
            write(STDOUT_FILENO, buf, n);
            close(fd);
            break;
        }
    }    
    return 0;
}

**阻塞:**调用结果返回值,当前程序被挂起,直到结果返回;

**非阻塞:**如果不能立即得到结果,则调用者不会阻塞当前线程;所以非阻塞的状态下,需要不断进行轮询;

6 fcntl改文件属性

函数:int fcntl(int fd, int cmd, …)

文件操作杂货铺,有诸多功能

int flags=fcntl(fd,F_GETFL);

获取文件状态:F_GETFL

设置文件状态:F_SETFL

位图:是一串二进制数,使用0或者1代表某个功能是否打开

和stm32 的pin脚定义类似

将标准输入的属性修改成 非阻塞状态 的代码如下:

int flags=fcntl(STDIN_FILENO,F_GETFL);
flags|=O_NONBLOCK;
fcntl(STDIN_FILENO,F_GETFL,flags)

头文件:#include <unistd> #include <fcntl.h>

功能:用于改变文件的状态

参数:fd是对应的文件描述符,cmd是对应的指令,…是可选项,是由cmd决定的参数,通常情况下是整数;

F_GETFD (void)
    Return  (as  the function result) the file descriptor flags; arg is ignored.
F_SETFD (int)
    Set the file descriptor flags to the value specified by arg.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值