Linux (open、write、read、close、lseek、chmod、sync)操作文件的函数详解

news2024/11/29 7:49:35

目录

一、文件操作方式

二、Linux底层文件操作

1. open

2. write

3. read

4. close

5. lseek

6. chmod

7. sync、syncfs、fsync、fdatasync

三、 Linux 系统调用

四、总结


linux中,一切皆文件(网络设备除外)

硬件设备也“是”文件,通过文件来使用设备

目录(文件夹)也是一种文件

这篇文章将记录open、write、read、close、lseek等Linux系统函数的用法。

补充:time 命令

        time命令分别输出:

        real - 程序总的执行时间;

        usr - 该程序本身所消耗的时间;

        sys - 系统调用所消耗的时间.


一、文件操作方式

1. 文件描述符  fd

         是一个 >= 0 的整数

         每打开一个文件,就创建一个文件描述符,通过文件描述符来操作文件

         多次打开同一个文件,可得到多个不同的文件描述符。

         预定义的文件描述符:(具体请看下面 课外补充)

         0标准输入(stdin),对应于已打开的标准输入设备(键盘)

         1标准输出(stdout),对应于已打开的标准输出设备(控制台)

         2标准错误(stderr), 对应于已打开的标准错误输出设备(控制台)        

这里可以使用write在控制台输出信息,跟printf性质一样,例:

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

int main(void) {

    char buf[1024];

    memset(buf, 0, sizeof(buf));
    read(0, buf, sizeof(buf));     // 标准输入

    printf("%s", buf);             // 输出到控制台

    write(1, buf, sizeof(buf));    // 标准输出到控制台

    write(2, buf, sizeof(buf));    // 标准错误输出到控制台

    return 0;
}

运行结果:

ygt@YGT:~/echo_server/test$ gcc demo1.c -o demo1
ygt@YGT:~/echo_server/test$ ./demo1
使用read输入。。。。
使用read输入。。。。
使用read输入。。。。
使用read输入。。。。
ygt@YGT:~/echo_server/test$

第一条信息是我们手动输入的,因为程序运行后,read函数通过 0 这个文件描述符,在等待我们输入;

第二条输出信息是printf函数输出的;

第三条输出信息是write函数通过 1 这个文件描述符输出的;

第四条输出信息是write函数通过 2 这个文件描述符输出的。

1 和 2 都是一样输出到控制台,就他们的性质不一样而已。     

2. 使用底层文件操作(系统调用)

           比如:read、write

           可使用man 2 查看

           (将在下面进行讲解)

3. 使用I/O库函数

             比如:fread

             可使用man 3 查看

            (就是C语言的文件操作函数,这里不进行讲解)

  

课外补充:

我们运行以下程序:

#include <unistd.h>

int main(void) {

    char buf[1024] = "0, 1, 2 到底是个什么?\n";
    while (1) {
        write(1, buf, sizeof(buf));
        sleep(1);
    }

    return 0;
}

编译运行:

可以看到,其每一秒都会向控制台输出一段字符串。

接下来我们重写打开一个终端,在终端运行以下命令查看demo3程序的进程号:

 ps -ef | grep demo3        注意:demo3是上面运行的程序名字

可以看到,./demo3的程序的进程号是 6893.

然后执行以下命令:

ll /proc/6893/fd/

我们通过查看对应程序进程号里的fd文件夹,就可以看出,Linux系统已经给./demo3这个程序默认创建了三个fd,分别是0,1,2;他们分别对应也就是标准输入标准输出标准出错

所以这也就是为什么我们可以使用 write read 函数和 01进行输入和输出的操作了。

注意:

/proc/6898/ 这个文件夹是Linux系统为程序./demo3临时创建的文件夹,当这个程序结束,Linux也会随之将他们删除掉。

任何程序已启动Linux系统都会为他们创建一个属于他们自己的对应进程号的文件夹,这个进程号是随机的,文件夹的名字是对应进程号的。

例:

将./demo3这个程序退出后,再次运行命令:ll /proc/6893/fd/


二、Linux底层文件操作

1. open

   

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

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

描述:打开指定路径的文件,返回一个文件描述符。

pathname

        文件路径名;

flags

        打开文件的方式;

mode

        设置文件的权限;

返回值

        成功:返回新的文件描述符;

        失败:-1,并且设置错误编号 errno ,可用( strerror(errno) ) 查看;

注意

        返回的文件描述符是该进程未打开的最小的文件描述符;

(1) 描述

        在Linux中,使用命令:man 2 open  

(2) 打开方式        

O_RDONLY    只读

O_WRONLY    只写

O_RDWR      读写

O_CREAT     如果文件不存在,则创建该文件,并使用第3个参数设置权限,如果文件存在 ,则只打开文件;

O_EXCL      如果同时使用O_CREAT而且该文件又已经存在时,则返回错误, 用途:以防止多个进程同时创建同一个文件;

O_APPEND    尾部追加方式(打开后,文件指针指向文件的末尾);

O_TRUNC     若文件存在,则长度被截为0,属性不变.

        例:  open("/dev/hello", O_RDONLY|O_CREAT|O_EXCL, 0777)         

        多个设置可用 “|” 进行分割。

            

(3) 参数3 (设置权限

        当参数2使用了O_CREAT时,就得使用参数3             

        S_I(R/W/X)(USR/GRP/OTH)

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  其他人拥有 执行 权限

        可以发现,读写执行权限,是由 + + 执行 而得来的;

        例如用户的 00700,是由 00400 + 00200 + 00100 而得来的。

        例:

                S_IRUSR | S_IWUSR    文件的所有者对该文件可

                (八进制表示法)00600     文件的所有者对该文件可

                多个设置可用 “|” 进行分割。

代码示例:          

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

int main(void) {

    int fd = 0;
    char fileName[] = "test.txt";

    // 读写,如果文件不存在则创建,如果文件存在则报错(O_EXCL);
    // 设置本用户拥有读写执行权限,同组用户拥有读写权限,其他人拥有读权限
    // fd = open(fileName, O_RDWR|O_CREAT|O_EXCL, 00764); // 可以使用数字代替参数三
    fd = open(fileName, O_RDWR|O_CREAT|O_EXCL, S_IRWXU|S_IRGRP|S_IWGRP|S_IROTH);
    if (-1 == fd) {
        fprintf(stderr, "open %s fail. reason: %s\n", fileName, strerror(errno));
        exit(-1);
    }

    printf("open %s successful!\n", fileName);

    // 关闭文件
    close(fd);

    return 0;
}

运行结果:

ygt@YGT:~/echo_server/test$ gcc open.c -o open
ygt@YGT:~/echo_server/test$ ./open
open test.txt successful!

ygt@YGT:~/echo_server/test$ ll
-rwxrwxr-x 1 ygt  ygt  8945 11月 10 16:02 open*
-rw-rw-r-- 1 ygt  ygt   554 11月 10 16:02 open.c
-rwxrw-r-- 1 ygt  ygt     0 11月 10 16:02 test.txt*

可以看出文件已正确运行!且test.txt的权限也是正确设置的 -rwxrw-r--.

test.txt 文件已经存在,当我们再一次运行 ./open 会怎么样呢?

出现报错了,报错提示“File exists”,文件已经存在了。

因为我们设置了O_EXCL,当文件存在时,他会进行报错处理!

2. write

#include <unistd.h>

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

描述:write()从指向buf的缓冲区向文件描述符fd引用的文件写入count个字节。

fd:

        文件描述符;

buf:

        需要写入的数据;

count

        指定最多写入的大小;

返回值

        成功: 返回写入的字节数

        失败: 返回 -1,并且设置错误编号 errno ,可用( strerror(errno) ) 查看.

描述

        在Linux中,使用命令:man 2 write

注意:是从文件的当前指针位置写入!文件刚打开时,文件的位置指针指向文件头.

代码示例:

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


#define W_LEN   1024

int main(void) {

    int ret = 0;
    int fd = -1;
    char fileName[] = "test.txt";
    char buf[W_LEN] = "这是要写入的内容:Hello write!";

    // 打开以文件,只写方式打开,不懂00764是什么意思上面open介绍
    fd = open(fileName, O_WRONLY|O_CREAT|O_EXCL, 00764);
    if (-1 == fd) {
        fprintf(stderr, "open %s fail. reason: %s\n", fileName, strerror(errno));
        exit(-1);
    }

    // 往文件中写入数据
    ret = write(fd, buf, strlen(buf));
    if(-1 == ret) {
        fprintf(stderr, "write error. reason: %s\n", strerror(errno));
        exit(-2);
    }

    printf("write successful! write lenght: %d\n", ret);

    close(fd);

    return 0;
}

运行结果:

ygt@YGT:~/echo_server/test$ gcc write.c -o write
ygt@YGT:~/echo_server/test$ ./write
write successful! write lenght: 41
ygt@YGT:~/echo_server/test$
ygt@YGT:~/echo_server/test$ cat test.txt
这是要写入的内容:Hello write!ygt @YGT:~/echo_server/test$
ygt@YGT:~/echo_server/test$

3. read

#include <unistd.h>

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

描述:read()尝试从文件描述符fd上读取数据放入buf中,读count字节。

fd:

        文件描述符;

buf:

        存储读取到的数据,一般传char *类型或字符数组;

count

        指定最多读取的大小;表示最多能接受的字节数,而不是指一定要读取的字节数;

返回值

        成功: 返回读取到的字节数;如果返回 0,表示文件读完了;

        失败: 返回 -1,并且设置错误编号 errno ,可用( strerror(errno) ) 查看.

描述

        在Linux中,使用命令:man 2 read

注意:是从文件的当前指针位置读取!文件刚打开时,文件的位置指针指向文件头.

代码示例:

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


#define R_LEN   1024

int main(void) {

    int ret = 0;
    int fd = -1;
    char fileName[] = "test.txt";
    char buf[R_LEN] = { '\0' };

    // 打开以文件,只读方式打开,不懂00764是什么意思上面open介绍
    fd = open(fileName, O_RDONLY, 00764);
    if (-1 == fd) {
        fprintf(stderr, "open %s fail. reason: %s\n", fileName, strerror(errno));
        exit(-1);
    }

    // 从文件中读取数据
    ret = read(fd, buf, sizeof(buf));
    if(-1 == ret) {
        fprintf(stderr, "read error. reason: %s\n", strerror(errno));
        exit(-2);
    }

    printf("read successful! read lenght: %d\n", ret);
    printf("%s\n", buf);
    //write(1, buf, strlen(buf));

    close(fd);

    return 0;
}

运行结果:

ygt@YGT:~/echo_server/test$ gcc read.c -o read
ygt@YGT:~/echo_server/test$ ./read
read successful! read lenght: 41
这是要写入的内容:Hello write!
ygt@YGT:~/echo_server/test$

4. close

#include <unistd.h>

int close (int fd);

描述:close()关闭一个文件描述符,这样它就不再引用任何文件,可以被重用。

fd:

        文件描述符;

返回值

        成功: 返回 0

        失败: 返回 -1,并且设置错误编号 errno ,可用( strerror(errno) ) 查看.

描述

        在Linux中,使用命令:man 2 close

        终止指定文件描述符与对应文件之间的关联,并释放该文件描述符,即该文件描述符可被重新使用.

当出现错误后,错误形式有三种:

        (1). EBADF          fd不是有效的打开文件描述符;

        (2). EINTR           close()调用被一个信号打断;

        (3). EIO                I/O错误.

代码示例:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

int main(void) {

    int ret = -1;
    int fd = 0;
    char fileName[] = "test.txt";

    fd = open(fileName, O_RDWR|O_CREAT, 00764);
    if (-1 == fd) {
        fprintf(stderr, "open %s fail. reason: %s\n", fileName, strerror(errno));
        exit(-1);
    }

    printf("open %s successful!\n", fileName);

    // 关闭文件
    ret = close(fd);
    if (-1 == ret) {
        int err = errno;
        fprintf(stderr, "close error. reason: %s\n", strerror(err));

        // 错误原因
        if (EBADF == err) {
            printf("fd不是有效的打开文件描述符\n");
        } else if (EINTR == err) {
            printf("close()调用被一个信号打断\n");
        } else if (EIO == err) {
            printf("I/O错误\n");
        }

        exit(-2);
    }

    printf("close fd successful!\n");

    return 0;
}

运行结果:

ygt@YGT:~/echo_server/test$ gcc close.c -o close
ygt@YGT:~/echo_server/test$ ./close
open test.txt successful!
close fd successful!
ygt@YGT:~/echo_server/test$

我们模拟将错误的文件描述符传参给close函数 :

        在   ret = close(fd);  这行代码前面加上:fd = -1;

        然后再运行程序运行结果如下:

达到我们的预期!

注意:

        如果在日常的demo中close()之后程序就结束了,那么也就无需对close的返回进行判断了,因为程序结束后,系统会回收所有资源;但是如果是在大型项目中,close之后还有很多其他操作,且文件fd的开销很大时,得进行返回值的判断,否则有可能会造成不必要的资源浪费。例如报错是EINTR,我们可以再一次调用close进行关闭fd!

5. lseek

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

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

描述:移动文件光标偏移。

fd:

        文件描述符;

offset:

        文件指针(光标)移动的大小;往文件头部移动设负数,往文件尾部移动设正数

whence:

        文件指针(光标)移动的依据;

返回值

        成功: 返回 移动后文件指针的所在位置

        失败: 返回 -1,并且设置错误编号 errno ,可用( strerror(errno) ) 查看.

描述

        在Linux中,使用命令:man 2 lseek

        移动文件光标指针在文件的位置,方便read和write操作!

文件指针移动的依据:

        SEEK_SET        相对于文件头部开始偏移;

                例:移动文件指针(光标)到第100个位置处

                sleek(fd, 100, SEEK_SET);        // 直接从开头位置往文件尾部移动100即可

        SEEK_CUR        从当前位置开始偏移;

                例:文件指针已经再100位置了,现在把他移动到80的位置

                sleek(fd, -20, SEEK_CUR);        // 在文件指针当前位置往文件头部移动-20个位置即可

        SEEK_END        相对于文件尾部开始偏移;

                例:文件指针已经再80位置了,现在把他移动到倒数第5个位置

                sleek(fd, -5, SEEK_END);        // 直接在文件尾部往文件头部移动-5个位置即可

代码示例:

需求,移动文件光标到100的位置,然后读取100个字节打印输出。

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


#define R_LEN           1024
#define READ_LEN        100

int main(void) {

    int ret = 0;
    int fd = -1;
    char fileName[] = "lseek.c";
    char buf[R_LEN] = { '\0' };

    fd = open(fileName, O_RDONLY);
    if (-1 == fd) {
        fprintf(stderr, "open %s fail. reason: %s\n", fileName, strerror(errno));
        exit(-1);
    }

    // 从文件头部往文件尾部移动100位置
    ret = lseek(fd, 100, SEEK_SET);
    if (-1 == ret) {
        fprintf(stderr, "lseek error. reason: %s\n", strerror(errno));
        exit(-2);
    }

    // 从文件中读取100个字节
    ret = read(fd, buf, READ_LEN);
    if(-1 == ret) {
        fprintf(stderr, "read error. reason: %s\n", strerror(errno));
        exit(-3);
    }

    printf("read successful! read lenght: %d\n", ret);
    printf("%s\n", buf);
    //write(1, buf, strlen(buf));

    close(fd);

    return 0;
}

运行结果:

可以通过SEEK_END来获取文件大小

int fileSize = lseek(fd, 0, SEEK_END);

6. chmod

#include <sys/stat.h>

int chmod (const char *path, mode_t mode);
int fchmod (int fd, mode_t mode);

描述:修改文件的权限。

fd:

        文件描述符;

path

        文件路径名;

mode

        修改的文件的权限;

返回值

        成功:返回 0

        失败:-1,并且设置错误编号 errno ,可用( strerror(errno) ) 查看

(1) 描述

        在Linux中,使用命令:man 2 chmod  

        当文件已打开,那么使用fchmod函数,如果文件没打开,那么使用chmod函数。

(2) 参数二 mode (修改的权限

        S_I(R/W/X)(USR/GRP/OTH)

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  其他人拥有 执行 权限

        可以发现,读写执行权限,是由 + + 执行 而得来的;

        例如用户的 00700,是由 00400 + 00200 + 00100 而得来的。

        例:

                S_IRUSR | S_IWUSR    文件的所有者对该文件可

                (八进制表示法)00600     文件的所有者对该文件可

                多个设置可用 “|” 进行分割。

例:

新建文件 test.txt, 查看其权限:

touch test.txt

可以看到,默认的权限是 读写|读|读

即本用户有 读写 权限,同组其他用户有 读 的权限;其他组用户有 读 的权限。

fchmod:将test.txt文件的权限修改为 执行|执行|执行

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

int main(void) {

    int ret = 0;
    int fd = 0;
    char fileName[1024] = "test.txt";

    //fd = open(fileName, O_RDWR|O_CREAT|O_EXCL, S_IRWXU|S_IRGRP|S_IWGRP|S_IROTH);
    fd = open(fileName, O_RDWR, 00764); // 权限:读写执行|读写|读
    if (-1 == fd) {
        fprintf(stderr, "open %s fail. reason: %s\n", fileName, strerror(errno));
        exit(-1);
    }


    // 修改文件权限
    //ret = fchmod(fd, S_IXUSR|S_IXGRP|S_IXOTH);
    ret = fchmod(fd, 00111);    // 修改权限为:执行|执行|执行
    if (-1 == ret) {
        fprintf(stderr, "fchmod %s fail. reason: %s\n", fileName, strerror(errno));
        exit(-2);
    }

    printf("fchmod successful!\n");

    close(fd);

    return 0;
}

运行结果:

root@YGT:/home/ygt/echo_server/test# gcc fchmod.c -o fchmod
root@YGT:/home/ygt/echo_server/test# ./fchmod
fchmod successful!
root@YGT:/home/ygt/echo_server/test#
root@YGT:/home/ygt/echo_server/test# ll
---x--x--x 1 root root    0 11月 16 12:04 test.txt*
root@YGT:/home/ygt/echo_server/test#

可以看到,test.txt文件的权限已经被修改为 执行|执行|执行 了,即用户只有 执行 权限,同组的用户也只有 执行 权限,其他组的用户也只有 执行 权限。

chmod:将test.txt文件的权限修改为 读|读|读

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>   // chmod

int main(void) {

    int ret = 0;

    // 修改文件权限
    //ret = chmod("test.txt", S_IRUSR|S_IRGRP|S_IROTH);
    ret = chmod("test.txt", 00444);     // 修改权限为:读|读|读
    if (-1 == ret) {
        fprintf(stderr, "fchmod %s fail. reason: %s\n", "test.txt", strerror(errno));
        exit(-1);
    }

    printf("fchmod successful!\n");

    return 0;
}

运行结果:

root@YGT:/home/ygt/echo_server/test# gcc chmod.c -o chmod
root@YGT:/home/ygt/echo_server/test# ./chmod
fchmod successful!
root@YGT:/home/ygt/echo_server/test#
root@YGT:/home/ygt/echo_server/test# ll
-r--r--r-- 1 root root    0 11月 16 12:04 test.txt
root@YGT:/home/ygt/echo_server/test#

可以看到,test.txt文件的权限已经被修改为 读|读|读 了,即用户只有 读 权限,同组的用户也只有 读 权限,其他组的用户也只有 读 权限。

7. sync、syncfs、fsync、fdatasync

Linux同步机制。

简单来讲,使用上面介绍的write后,或者做其他对文件有修改的操作后,需要进行同步,将数据从内核缓冲区写入磁盘;否则如果断电或系统宕机,即使已经调用了write或fwrite函数,数据都还没有写入磁盘。

#include <unistd.h>

1. sync

        void sync(void);

        描述将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束! sync不会执行失败!        

2. syncfs

        int syncfs(int fd);

        描述syncfs()类似于sync(),但只同步包含由打开的文件描述符fd引用的文件的文件系统。

        fd:

                文件描述符;

        返回值

                成功: 返回 0

                失败: 返回 -1,并且设置错误编号 errno ,可用( strerror(errno) ) 查看. 

3. fsync

        int fsync(int fd);        // 推荐使用!

        描述fsync()将文件描述符fd引用的文件(即修改的缓冲区缓存页)的所有修改的内核数据传输(“刷新”)到磁盘设备(或其他永久存储设备),以便即使在系统崩溃或重新启动后也可以检索所有更改的信息。这包括写入磁盘缓存或刷新磁盘缓存(如果存在的话)。他会一直阻塞,直到执行完毕!

        fd:

                文件描述符;

        返回值

                成功: 返回 0

                失败: 返回 -1,并且设置错误编号 errno ,可用( strerror(errno) ) 查看. 

4. fdatasync

        int fdatasync(int fd);

        描述fdatasync()类似于fsync(),但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。

        fd:

                文件描述符;

        返回值

                成功: 返回 0

                失败: 返回 -1,并且设置错误编号 errno ,可用( strerror(errno) ) 查看. 

使用案例一:C语言 I/O

FILE *fp = NULL;
fp = fopen(filename,"rw" );

// 函数int fileno(FILE*stream) 把文件流描述符(fp)转换为文件描述符(fd)
int fd = fileno(fp);

char buff[64] = "hello world";
fwrite(buff, sizeof(buff), 1, fp);

fflush(fp);
fsync(fd);
fclose(fp);

使用案例二:Linux系统调用

int fd = open(filename, O_RDWR);
char buff[64] = "hello world";

write(fd, buff,sizeof(buff));

fsync(fd);
close(fd);


三、 Linux 系统调用

根据我个人的理解,系统调用就是调用Linux内核中的函数。

Linux系统中分为 用户空间内核空间

用户空间是我们程序运行所在的空间;

例如,我们的程序中有个open函数,open函数内部会调用函数syscall函数,这个函数就会调用系统内部的sys_open函数,进入内核空间进行相应操作,然后再将相应返回值进行返回;

每执行一次上面介绍的open、write、read等函数,就是再执行一次系统调用!

为什么要有系统调用?

1. 举个例子,我们所用的硬盘有很多品牌,但每个品牌所用的读取写入参数接口函数可能都是不一样的(硬件编码),将用户与底层分离开来,用户只需调用read、write等函数即可对所有类型的硬盘做操作;

2. 为了安全性。分为用户空间和内核空间,用户没法直接对内核空间做操作,确保系统的安全性;

3. 可移植性。不同平台,不同硬件,不同Linux系统他们的内核都差不多,都会有open、write等函数。

系统调用两个关键要素:

1. 系统调用号

        每个系统调用被赋予一个系统调用号,与具体的系统调用相关联。

        

2. 系统调用表

        内核维护系统调用表,保存系统调用函数的起始地址,系统调用号对应系统调用在调用表中的偏移量。

        

系统调用会影响效率嘛?

        频繁使用底层系统调用会影响程序的执行效率。

        因为频繁的从用户空间去到内核空间去访问,然后再返回到用户空间去继续执行,会有消耗。

例:

两个程序:写一个1G的文件,定义数组buf,大小为2048和16,循环每次往数组写入2048和16字节大小,比较两次所耗费的时间。

#define W_LEN           16
#define W_LEN           2048

循环次数:

一个程序写入次数:(1024*1024*1000) / 16 == 65,536,000 次

一个程序写入次数:(1024*1024*1000) / 2048 == 512,000 次


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


//#define W_LEN           16
#define W_LEN           2048
#define FILE_SIZE       (1024*1024*1000)

int main(void) {

    int ret = 0;
    int fd = -1;
    char fileName[] = "test.txt";
    char buf[W_LEN];


    // 打开以文件,只写方式打开,不懂00764是什么意思上面open介绍
    fd = open(fileName, O_WRONLY|O_CREAT|O_EXCL, 00764);
    if (-1 == fd) {
        fprintf(stderr, "open %s fail. reason: %s\n", fileName, strerror(errno));
        exit(-1);
    }

    memset(buf, '6', sizeof(buf));

    int count = FILE_SIZE / sizeof(buf);    // 次数
    int i = 0;
    for (; i < count; i++) {
        // 往文件中写入数据
        ret = write(fd, buf, strlen(buf));
        if(-1 == ret) {
            fprintf(stderr, "write error. reason: %s\n", strerror(errno));
            exit(-2);
        }
    }

    printf("write successful!\n");

    close(fd);

    return 0;
}

运行程序,通过上面所介绍的,使用time参数去显示程序运行所耗费的时间:

gcc systemCall_2048.c -o systemCall_2048      // #define W_LEN    2048

gcc systemCall_2048.c -o systemCall_16        // #define W_LEN    16

可以看出,系统调用执行的次数越多,所耗费的时间就越长;所以,以此证明,多次执行系统调用是会耗费资源的

那如何解决这样的问题呢?

尽可能少调用系统接口,能一次搞定的避免多次!
另外,网上说,如果
多调用GLIBC库 和 GLIB

反正不会的话,就直接用C语言的 I/O 文件操作函数即可,即fopen、fwrite、fread等这类函数!


四、总结

Linux操作文件的函数已经介绍完毕。

我们学会的同时也要注意系统调用带来的后果,只要有了这方面的意识,在写C/C++代码时,就会注意到,就不会写出效率慢的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/166239.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣刷题记录——507.完美数、509. 斐波那契数、520. 检测大写字母

本专栏主要记录力扣的刷题记录&#xff0c;备战蓝桥杯&#xff0c;供复盘和优化算法使用&#xff0c;也希望给大家带来帮助&#xff0c;博主是算法小白&#xff0c;希望各位大佬不要见笑&#xff0c;今天要分享的是——《507.完美数、509. 斐波那契数、520. 检测大写字母》。 目…

InfluxDB + Grafana计算成功率

文章目录方式一 借助Grafana的Transfrom方式二 Influx子查询Transfrom介绍建议针对每类Metric&#xff0c;使用一个Metric&#xff0c;增加success的tag区分成功还是失败。 方式一 借助Grafana的Transfrom 第一步&#xff1a;新建2个Query Query Total: SELECT sum("coun…

安科瑞电气火灾监控系统在春晓161#地块人防工程的设计与应用

安科瑞 华楠摘要&#xff1a;本文简述了电气火灾监控系统的组成原理&#xff0c;分析了电气火灾监控系统在应用中的设计依据和相关规范。通过安科瑞剩余电流式电气火灾监控系统在春晓161#地块人防工程电气火灾监控系统项目的实例介绍&#xff0c;阐述了电气火灾监控系统功能的实…

c语言实现扫雷(详细讲解)

本篇介绍,讲解如何使用c语言实现扫雷小游戏. 金句分享: ✨✨✨爱你所爱,行你所行✨✨✨ 目录前言:一、游戏设计思路介绍:效果展示二、游戏的分步讲解2.1、主函数测试区&#xff08;test.c&#xff09;基本构成2.2、游戏中函数实现区(game.c) (重点)2.21、雷盘的创建与初始化函…

centos8 Ambari-2.7.6.3+HDP-3.3.1离线安装详细教程(附安装包)

自2021年1月31日开始,所有Cloudera软件都需要有效的订阅,且订阅费昂贵。此外,CDH6和HDP3将是CDH和HDP的最后企业版本,原有企业版用户无法继续获取新的功能和性能提升。至2022年3月份,CDH/HDP全部停止服务(EoS),用户没办法获取售后支持。由于生产环境系统升级到centos8,…

linux 中 PCIE 中断映射机制

PCIE 中断映射机制 1、 PCIE 中有三种中断方式&#xff0c; MSI&#xff0c;MSI-X 和INTx PCIe总线继承了PCI总线的所有中断特性&#xff08;包括INTx和MSI/MSI-X&#xff09;&#xff0c;以兼容早期的一些PCI应用层软件。 PCI总线最早采用的中断机制是INTx&#xff0c;这是…

基于Flink+kafka实时告警

引出问题 项目使用告警系统的逻辑是将实时数据保存到本地数据库再使用定时任务做判断&#xff0c;然后产生告警数据。这种方式存在告警的延时实在是太高了。数据从产生到保存&#xff0c;从保存到判断都会存在时间差&#xff0c;按照保存数据定时5分钟一次&#xff0c;定时任务…

智慧水务能效管理平台在污水处理厂电气节能中的应用

摘要&#xff1a;污水处理属于高能耗行业&#xff0c;会消耗大量的电能、燃料和药剂等&#xff0c;高能耗不仅会提升污水处理成本&#xff0c;还会加剧能源危机。所以&#xff0c;本文首先探究了污水处理厂耗能的原因&#xff0c;分析了污水处理与节能降耗的关系&#xff0c;然…

MyBatis-Plus数据安全保护(加密解密)

项目创建POM依赖 <dependency><!--MyBatis-Plus 企业级模块--><groupId>com.baomidou</groupId><artifactId>mybatis-mate-starter</artifactId><version>1.2.8</version> </dependency> <!-- https://mvnrepository…

git commit 命令详解

文章目录前言1. git commit 介绍2. git commit 使用3. git commit -m4. git commit -am5. git commit --amend6. commit 多行提交信息7. commit 背后到底发生了什么前言 CSDN 只用来做博客主站文章的转载 博客主站&#xff1a;https://www.itqaq.com 下面地址路径可能会发生变…

Java---中间件---Redis的常见命令和客户端使用

Redis的常见命令和客户端使用1.初识Redis1.1.认识NoSQL1.1.1.结构化与非结构化1.1.2.关联和非关联1.1.3.查询方式1.1.4.事务1.1.5.总结1.2.认识Redis1.3.安装Redis1.3.1.依赖库1.3.2.上传安装包并解压1.3.3.启动1.3.4.默认启动1.3.5.指定配置启动1.3.6.开机自启1.4.Redis桌面客…

VulnHub2018_DeRPnStiNK靶机总结

VulnHub2018_DeRPnStiNK靶机渗透总结 靶机下载地址: https://download.vulnhub.com/derpnstink/VulnHub2018_DeRPnStiNK.ova https://www.dropbox.com/s/8jqor3tuc3jhe1w/VulnHub2018_DeRPnStiNK.ova?dl0 打开靶机,使用nmap扫描出靶机的ip和开放的所有端口 可以看到,靶机开放…

从零开始学习Linux

Linux Linux内核版本&#xff1a;Linux内核运维开发小组&#xff0c;源码在不开源 Linux发行版本&#xff1a;由各大互联网/软件公司定制&#xff0c;开源 一个内核版本是有多种多样的发行版本 Ubuntu&#xff1a;以强大的桌面应用为主&#xff0c;吸收不少Windows用户&…

Docker部署jeecgboot微服务使用记录

docker安装和基础命令 docker安装 docker安装详细步骤 Docker命令 #进入容器 sudo docker exec -it 775c7c9ee1e1 /bin/bash # docker中 启动所有的容器命令 docker start $(docker ps -a | awk { print $1} | tail -n 2) # docker中 关闭所有的容器命令 docker stop $(doc…

(黑马C++)L09 C++类型转换 异常 输入输出流

一、C类型转换 类型转换&#xff08;cast&#xff09;是将一种数据类型转换成另一种数据类型&#xff0c;一般情况下要尽量少的去使用类型转换&#xff0c;除非解决非常特殊的问题。 &#xff08;1&#xff09;静态转换&#xff08;static_cast&#xff09; static_cast使用…

联合证券|内外利好共振 今年A股可更乐观一点

在经历了开年首周的快速拉升后&#xff0c;上星期A股商场全体高位盘整。职业板块从普涨转为快速轮动&#xff0c;前期领涨的新能源及大消费主线均出现了必定程度的回撤&#xff0c;由金融、信创板块接力“领跑”。 展望后市&#xff0c;指数在盘整后能否持续上攻&#xff1f;外…

解决前后端分离Vue项目部署到服务器后出现的302重定向问题

解决前后端分离Vue项目部署到服务器后出现的302重定向问题问题描述问题原因定位问题解决方案校验修改效果相关阅读写在最后问题描述 最近发现自己开发的vue前后端分离项目因为使用了spring security 安全框架&#xff0c;即使在登录认证成功之后再调用一些正常的接口总是会莫名…

Xilinx 7系列FPGA之Spartan-7产品简介

以最低的成本获得无与伦比的性能和功耗效率如果您对功耗或性能的要求与成本要求一样严苛&#xff0c;那么请使用 Spartan -7 FPGA。该系列采用 TSMC&#xff08;台积电&#xff09; 的 28nm HPL 工艺制造&#xff0c;将小尺寸架构的Xilinx 7 系列FPGA 的广泛功能和符合 RoHS 标…

结构体专题详解

目录 &#x1f94e;什么是结构体&#xff1f; ⚾结构体的声明 &#x1f3c0;简单结构体的声明 &#x1f3d0;结构体的特殊声明 &#x1f3c8;结构体嵌套问题 &#x1f3c9;结构体的自引用 &#x1f3b3;结构体的内存大小 &#x1f94c;结构体的内存对齐 ⛳内存对齐的优点 ⚽还…

SAP 服务器参数文件详细解析

一、SAP参数的说明 SAP参数的学习需要了解SAP参数的作用、参数的启动顺序、参数的配置&#xff1b; 1、参数的启动顺序 a) 启动Start profileb) 启动default profilec) 启动instance profile 2、参数的位置 a) 启动参数Start profile的位置&#xff1a;/usr/sap//SYS/prof…