当前位置: 首页 > article >正文

Linux下write函数

在 Linux 中,write 函数是操作系统提供的最基础的系统调用之一,用于向文件描述符写入数据。它的使用非常广泛,不仅仅限于普通文件,还包括管道、套接字、字符设备等。

Linux 中的 write 函数详解

一、函数定义与头文件

write 函数在 <unistd.h> 头文件中定义,函数原型如下:

#include <unistd.h>

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

二、参数详解

write 函数的参数有三个:

  1. fd:文件描述符,表示将数据写入到哪里。可以是文件、设备、套接字等。文件描述符是一个整数,通常是由 open 系统调用返回的。

    • 0:标准输入(stdin)
    • 1:标准输出(stdout)
    • 2:标准错误(stderr)
  2. buf:指向要写入的数据的缓冲区。这是一个 void 类型的指针,意味着它可以指向任何类型的数据。

  3. count:要写入的字节数。函数会尝试从 buf 中写入 count 个字节的数据。

三、返回值

write 函数的返回值为 ssize_t 类型,表示实际写入的字节数:

  • 返回值为正整数,表示成功写入的字节数,可能会小于 count(例如,由于磁盘已满等原因)。
  • 返回值为 0,表示没有写入任何数据。
  • 返回值为 -1,表示发生错误,并设置 errno 来提供进一步的错误信息。

四、示例代码

下面是一个简单的例子,向文件中写入数据:

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

int main() {
    const char *text = "Hello, Linux write function!\n";
    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    
    if (fd == -1) {
        perror("Failed to open file");
        return 1;
    }

    ssize_t bytes_written = write(fd, text, sizeof(text));

    if (bytes_written == -1) {
        perror("Failed to write to file");
        close(fd);
        return 1;
    }

    printf("Wrote %zd bytes to output.txt\n", bytes_written);

    close(fd);
    return 0;
}

五、常见错误处理

write 在发生错误时会返回 -1,并设置 errno,我们可以通过 perrorstrerror 来输出错误信息。常见的错误有:

  • EAGAIN:文件描述符是非阻塞的,但资源暂时不可用。
  • EBADF:文件描述符无效或没有写权限。
  • EFAULT:提供的缓冲区指针无效。
  • EINVAL:参数不合法,例如,尝试向不可写的文件描述符写数据。
  • EFBIG:试图写入超过文件系统大小限制的数据。
  • ENOSPC:磁盘空间不足。

六、write 的行为细节

  1. 写入大小可能小于请求的字节数
    write 函数不会保证一次性写入请求的所有字节。在某些情况下,尤其是在向网络套接字或管道写入数据时,write 可能会提前返回,写入的字节数少于 count。这种情况发生时,通常需要继续调用 write 直到所有数据都成功写入。

    例如:

    ssize_t total_written = 0;
    while (total_written < count) {
        ssize_t written = write(fd, buf + total_written, count - total_written);
        if (written == -1) {
            // 处理错误
            break;
        }
        total_written += written;
    }
    
  2. 写入缓冲区
    当我们向文件或设备写入数据时,write 可能并不会立即将数据写入物理磁盘,而是写入内核缓冲区。这种缓冲机制提高了写入性能,但意味着在数据真正落盘之前可能会有延迟。可以使用 fsyncfdatasync 确保数据被刷新到磁盘。

  3. 原子性
    write 操作是“原子”的,尤其是对于文件描述符指向的常规文件。也就是说,当多个进程同时写入同一个文件时,系统会保证这些写操作不会交错。但是,对于非常大的写操作(超过 PIPE_BUF 大小),可能无法保证原子性。

七、write 的使用场景

  1. 写入大小可能小于请求的字节数
    write 函数不会保证一次性写入请求的所有字节。在某些情况下,尤其是在向网络套接字或管道写入数据时,write 可能会提前返回,写入的字节数少于 count。这种情况发生时,通常需要继续调用 write 直到所有数据都成功写入。

    例如:

    ssize_t total_written = 0;
    while (total_written < count) {
        ssize_t written = write(fd, buf + total_written, count - total_written);
        if (written == -1) {
            // 处理错误
            break;
        }
        total_written += written;
    }
    

  2. 写入缓冲区
    当我们向文件或设备写入数据时,write 可能并不会立即将数据写入物理磁盘,而是写入内核缓冲区。这种缓冲机制提高了写入性能,但意味着在数据真正落盘之前可能会有延迟。可以使用 fsyncfdatasync 确保数据被刷新到磁盘。

  3. 原子性
    write 操作是“原子”的,尤其是对于文件描述符指向的常规文件。也就是说,当多个进程同时写入同一个文件时,系统会保证这些写操作不会交错。但是,对于非常大的写操作(超过 PIPE_BUF 大小),可能无法保证原子性。

  4. 写入文件
    这是最常见的使用场景。通过 open 打开文件,使用 write 写入数据,最后使用 close 关闭文件。

  5. 网络通信
    当使用套接字编程时,write 可以用于向网络连接发送数据。结合 socketconnect 函数,write 成为向服务器或客户端发送数据的基础。

  6. 管道通信
    在父子进程间通过管道传递数据时,也可以使用 write。创建管道后,父进程可以向管道写入数据,子进程可以从管道读取数据,反之亦然。

  7. read:与 write 相对应的系统调用,用于从文件描述符中读取数据。
  8. open:用于打开文件,返回文件描述符。
  9. close:用于关闭文件描述符,释放相关资源。
  10. fsync / fdatasync:用于强制将缓冲区中的数据同步到磁盘。

八、与 write 相关的其他系统调用

  • read:与 write 相对应的系统调用,用于从文件描述符中读取数据。
  • open:用于打开文件,返回文件描述符。
  • close:用于关闭文件描述符,释放相关资源。
  • fsync / fdatasync:用于强制将缓冲区中的数据同步到磁盘。

九、总结

write 函数是 Linux 编程中最基础的系统调用之一,广泛应用于文件、网络、进程间通信等场景。理解它的工作原理、潜在错误及如何正确使用,是系统级编程的关键。通过合理处理可能出现的部分写入、错误处理等细节问题,能够编写出更加健壮的代码。


http://www.kler.cn/a/306050.html

相关文章:

  • EMS(energy managment system)从0到1
  • leetcode hot100 将有序数组转化为二叉搜索树
  • React第十八节 useEffect 用法使用技巧注意事项详解
  • 2024年全球薄膜功率电感器行业总体规模、主要企业国内外市场占有率及排名
  • OpenCV学习——图像融合
  • 计算机网络B重修班-期末复习
  • PG表空间
  • Android命令行查看CPU频率和温度
  • 鲸天科技外卖会员卡系统更专业
  • Spring源码(12)-- Aop源码
  • 【Linux 从基础到进阶】自动化部署工具(Jenkins、GitLab CI/CD)
  • jdk知识
  • Excel数据清洗工具:提高数据处理效率的利器
  • verilog运算符优先级
  • TCP/IP网络编程概念及Java实现TCP/IP通讯Demo
  • 论文速递!Auto-CNN-LSTM!新的锂离子电池(LIB)剩余寿命预测方法
  • WEB打点
  • Metacritic 网站中的游戏开发者和类型信息爬取
  • OpenCV-轮廓检测
  • 《深度学习》PyTorch 手写数字识别 案例解析及实现 <下>
  • 编写并运行第一个spark java程序
  • 【JavaEE】初识⽹络原理
  • 计算机毕业设计 二手闲置交易系统的设计与实现 Java实战项目 附源码+文档+视频讲解
  • python-古籍翻译
  • Leetcode面试经典150题-148.排序链表
  • 16. 池化层的基本使用 -- nn.MaxPool2d