• 换个地方写helloworld


    1、Linux中I/O讲解

    今天,我们重新学习C语言的基础,特别是I/O口的讲解。

    所谓标准 I/O 库则是标准 C 库中用于文件 I/O 操作(譬如读文件、写文件等)相关的一系列库函数的集合,通常标准 I/O 库函数相关的函数定义都在头文件中,所以我们需要在程序源码中包含头文件。

    标准 I/O 库函数是构建于文件 I/O(open()、read()、write()、lseek()、close()等)这些系统调用之上的,譬如标准 I/O 库函数 fopen()就利用系统调用 open()来执行打开文件的操作、fread()利用系统调用 read()来执行读文件操作、fwrite()则利用系统调用 write()来执行写文件操作等等。

    大家需要有一个意识的认知,标准 I/O 与 文件I/O的区别:

    1. 虽然标准 I/O 和文件 I/O 都是 C 语言函数,但是标准 I/O 是标准 C 库函数,而文件 I/O 则是 Linux系统调用;
    2. 标准 I/O 是由文件 I/O 封装而来,标准 I/O 内部实际上是调用文件 I/O 来完成实际操作的;
    3. 可移植性:标准 I/O 相比于文件 I/O 具有更好的可移植性,通常对于不同的操作系统,其内核向应用层提供的系统调用往往都是不同,譬如系统调用的定义、功能、参数列表、返回值等往往都是不一样的;而对于标准 I/O 来说,由于很多操作系统都实现了标准 I/O 库,标准 I/O 库在不同的操作系统之间其接口定义几乎是一样的,所以标准 I/O 在不同操作系统之间相比于文件 I/O 具有更好的可移植性。
    4. 性能、效率:标准 I/O 库在用户空间维护了自己的 stdio 缓冲区,所以标准 I/O 是带有缓存的,而文件 I/O 在用户空间是不带有缓存的,所以在性能、效率上,标准 I/O 要优于文件 I/O。

    2、open函数

    在 Linux 系统中要操作一个文件,需要先打开该文件,得到文件描述符,然后再对文件进行相应的读写操作(或其他操作),最后在关闭该文件;open 函数用于打开文件,当然除了打开已经存在的文件之外,还可以创建一个新的文件;

    #include 
    #include 
    #include 
    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在 Linux 系统下,可以通过 man 命令(也叫 man 手册)来查看某一个 Linux 系统调用的帮助信息,man命令可以将该系统调用的详细信息显示出来,譬如函数功能介绍、函数原型、参数、返回值以及使用该函数所需包含的头文件等信息;man 更像是一份帮助手册,所以也把它称为 man 手册,当我们需要查看某个系统调用的功能介绍、使用方法时,不用在上网到处查找,直接通过 man 命令便可以搞定,man 命令用法如下所示:

    man 2 open #查看 open 函数的帮助信息
    
    • 1

    Tips:man 命令后面跟着两个参数,数字 2 表示系统调用,man 命令除了可以查看系统调用的帮助信息外,还可以查看 Linux 命令(对应数字 1)以及标准 C 库函数(对应数字 3)所对应的帮助信息;最后一个参数 open 表示需要查看的系统调用函数名。
    在这里插入图片描述
    在这里插入图片描述
    open函数解析
    pathname:字符串类型,用于标识需要打开或创建的文件,可以包含路径(绝对路径或相对路径)信息,譬如:“./src_file”(当前目录下的 src_fil件)、"/home/dengtao/hello.c"等;如果 pathname 是一个符号链接,会对其进行解引用。
    flags:调用 open 函数时需要提供的标志,包括文件访问模式标志以及其它文件相关标志,这些标志使用宏定义进行描述,都是常量,open 函数提供了非常多的标志,我们传入 flags 参数时既可以单独使用某一个标志,也可以通过位或运算(|)将多个标志进行组合。这些标志介绍如下:
    在这里插入图片描述

    3.Write写文件

    调用 write 函数可向打开的文件写入数据,其函数原型如下所示(可通过"man 2 write"查看):

    man 2 write
    
    • 1

    在这里插入图片描述

    #include 
    ssize_t write(int fd, const void *buf, size_t count);
    
    • 1
    • 2

    查看函数调用方法:
    fd:文件描述符。关于文件描述符,前面已经给大家进行了简单地讲解,这里不再重述!我们需要将进
    行写操作的文件所对应的文件描述符传递给 write 函数。
    buf:指定写入数据对应的缓冲区。
    count:指定写入的字节数。
    返回值:如果成功将返回写入的字节数(0 表示未写入任何字节),如果此数字小于 count 参数,这不是错误,譬如磁盘空间已满,可能会发生这种情况;如果写入出错,则返回-1。

    4.read读文件

    调用 read 函数可从打开的文件中读取数据,其函数原型如下所示(可通过"man 2 read"查看):

    man 2 read
    
    • 1

    在这里插入图片描述

    #include 
    ssize_t read(int fd, void *buf, size_t count);
    
    • 1
    • 2

    函数参数和返回值含义如下:
    fd:文件描述符。与 write 函数的 fd 参数意义相同。
    buf:指定用于存储读取数据的缓冲区。
    count:指定需要读取的字节数。
    返回值:如果读取成功将返回读取到的字节数,实际读取到的字节数可能会小于 count 参数指定的字节数,也有可能会为 0,譬如进行读操作时,当前文件位置偏移量已经到了文件末尾。实际读取到的字节数少于要求读取的字节数,譬如在到达文件末尾之前有 30 个字节数据,而要求读取 100 个字节,则 read 读取成功只能返回 30;而下一次再调用 read 读,它将返回 0(文件末尾)。

    5. lseek函数

    对于每个打开的文件,系统都会记录它的读写位置偏移量,我们也把这个读写位置偏移量称为读写偏移量,记录了文件当前的读写位置,当调用 read()或 write()函数对文件进行读写操作时,就会从当前读写位置偏移量开始进行数据读写。
    读写偏移量用于指示 read()或 write()函数操作时文件的起始位置,会以相对于文件头部的位置偏移量来表示,文件第一个字节数据的位置偏移量为 0。
    当打开文件时,会将读写偏移量设置为指向文件开始位置处,以后每次调用 read()、write()将自动对其进行调整,以指向已读或已写数据后的下一字节,因此,连续的调用 read()和 write()函数将使得读写按顺序递增,对文件进行操作。我们先来看看 lseek 函数的原型,如下所示(可通过"man 2 lseek"查看):

    #include 
    #include 
    off_t lseek(int fd, off_t offset, int whence);
    
    • 1
    • 2
    • 3

    函数参数和返回值含义如下:
    fd:文件描述符。
    offset:偏移量,以字节为单位。
    whence:用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):
    1).SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头部开始算);
    2).SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处,offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移;
    3).SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。
    返回值:成功将返回从文件头部开始算起的位置偏移量(字节为单位),也就是当前的读写位置;发生错误将返回-1。

    参考例程

    将hello world 写入文件并读出;

    #include 
    #include 
    #include 
    #include 
    #include 
    
    int  main(void)
    {
        char buffer[1024];
        int fd1,fd2; //fd1写文件,fd2读文件
        int ret;
      /************写文件***************/
      	//通过open参数,对用户赋予txt文件的使用权限(也可以单独使用终端修改权限)
        fd1 = open("./test.txt", O_WRONLY | O_CREAT | O_EXCL , 644);
        if (-1 == fd1)
        {
            printf("open1 error\n");
            return 1;
        }
    
        printf("open1 is ok\n");
    
        ret = write(fd1, "hello world", 11 );
        if (-1 == ret )
        {
            printf("write error\n");
            close(fd1);
            return 1;
        }
    
        printf("write %d bytes Ok! \n",ret);
        close(fd1);
    
        /************读文件***************/
       fd2 = open("./test.txt", O_RDONLY );
        if (-1 == fd2)
        {
            printf("open2 error\n");
            return 1;
        }
        printf("open2 is ok\n");
    
    
        ret = read(fd2, buffer, 11 );
        if (-1 == ret )
        {
            printf("read error\n");
            close(fd2);
            return 1;
        }
        printf("read %d bytes %s\n",ret,buffer);
        close(fd2);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    本文参考正点原子的嵌入式LinuxC应用编程

  • 相关阅读:
    聊聊绩效考核:松松网编辑跑路了吗?
    [ vulhub漏洞复现篇 ] Apache Log4j Server 反序列化命令执行漏洞 CVE-2017-5645
    手写RISC-V处理器--开篇
    校园二手市场交易平台(JAVA,SSM,BOOTSTRAP,JSP,AJAX,MYSQL)
    并发学习心得
    数字藏品这么值钱?数字产品能否在元宇宙中呈现?什么是NFG?
    【博客718】时序数据库基石:LSM Tree(log-structured merge-tree)
    C++之浅拷贝、深拷贝、拷贝构造函数、拷贝赋值运算符、自定义的深拷贝函数应用总结(二百二十九)
    【NestJS系列】核心概念:Module模块
    7. TTL 延迟队列
  • 原文地址:https://blog.csdn.net/weixin_52694360/article/details/127991695