阅读分享 -- 《Linux 系统编程 第二版》I
思来想去,还是先从阅读分享开始吧,系统编程内容暂时还没有头绪...
概述
这本书大致分为文件、进程、线程、内存、IPC通讯、Linux时钟,这六大部分。下面时大致的概念归纳(本文内容展开也将按此顺序展开)
- 文件(1-4章、第8章) - Linux 的文件概念 - 文件基本IO(包含字符流/文本流/字节流/文件属性/目录/设备文件) - 高级IO(磁盘IO) - 多路IO复用
- 进程(5-6章) - 进程概念 - 进程管理 - 高级进程管理
- 线程
- 内存
- IPC 通讯 - 并发 - 资源/数据竞争
- Linux时钟
chapter I 文件
Linux 文件概念
Linux 下一切皆文件。
Linux 下可分文普通文件和特殊文件:
- 一般文件:字符流文件[比如文本文件]
- 特殊文件:块设备文件,字符设备文件,命名管道,Unix套接字文件
Linux 文件系统存储原理
Linux 同过VFS虚拟文件系统对上提供了一个统一的接口使得使用者无需关心底层存储系统是那一中文件系统格式,各个格式的系统可以以插件的形式嵌入到虚拟文件系统中。以下是文件Linux文件存储的简易示意图
Linux 文件系统中并不以文件名问文件的为以查找索引,而是使用表加序列映射(多级索引表)的方式去管理和存储数据也就是inode。
- super blk
super block
存储了文件系统的元信息 -- Linux 要求接入VFS 的文件系统都需要实现此模块,在不在磁盘中驻留的RAM 文件系统在也须生成此模块(一般在生成时生成此模块)
eg: 文件系统的大小、块大小、inode 数量、挂载次数等
还存储了文件系统的状态信息,以便在系统崩溃或意外关机时进行文件系统检测和修复
- 块组描述符
- block bitmap
这个块位图用于跟踪该块组中每个数据块的使用情况, 用于维护文件系统的空间使用情况
块位图中的每一个 bit 对应一个数据块
如果位为 1,则表示对应的数据块已被使用
如果位为 0,则表示对应的数据块为空闲
- inode bitmap
用于跟踪该块组中每个 inode 的使用情况
inode 位图中的每一位对应一个 inode
如果位为 1,则表示对应的 inode 已被使用;如果位为 0,则表示对应的 inode 为空闲
- inode table
inode 表,使用inode 号查表可获得对应结构体地址
- inode blk
index node block
- data blk
data block
打开一个文件发生了什么?
ext4 文件系统 inode 结构提部分源码
// linux-5.4.18/fs/ext4/ext4.h
/*
* Structure of an inode on the disk
*/
struct ext4_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size_lo; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Inode Change time */
__le32 i_mtime; /* Modification time */
__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks_lo; /* Blocks count */
__le32 i_flags; /* File flags */
......
__le32 i_block[EXT4_N_BLOCKS];/* Pointers to blocks */
Linux 直接管理文件名所建立的映射项,在Linux 世界下目录才是文件管理直接项(根目录也是如此)[目录下包含文件 - dir/file],这个目录文件存储在对应的datablock中。
目录项的索引是通过目录文件内部的哈希表(hash table)或 B+ 树实现的。当用户打开一个目录时,系统会读取目录文件中的目录项,并将它们缓存在内存中。当用户访问某个文件时,系统会根据文件名查找对应的目录项,获取该文件的 inode 号,然后根据 inode 号读取文件的内容。
用户在打开一个文件时系统做了一下几步工作:
- 在缓存在内存中目录属下对应文件名的inode
- 用inode号查inode table 读取对应inode结构体数据(元信息),做权鉴,若有权限则进行下一步
- 在元信息块中拿到datablk0地址
- 通过data blk0 中的地址寻址载入并访问磁盘中的数据
其他事项:
- i_block 数组存储了指向文件数据块的指针,用于定位和访问文件的实际数据。
- inode 也能够将数据存储在 inode 本身 i_block 数组中。这叫做 Inlining。这种存储方法具有节省空间的优点,因为不需要数据块。它还通过避免更多的磁盘访问来获取数据,从而增加了查找时间。
- 像 ext4 这样的一些文件系统有一个名为 inline_data 的选项。启用后,它允许操作系统以这种方式存储数据。由于大小限制,内联只适用于非常小的文件。如果大小不超过 60 字节,Ext2 和更高版本通常会以这种方式存储软链接信息。
内嵌存储(Inlining)是一种特定文件系统(如 ext4)提供的功能,它允许将小量的数据直接存储在 inode 本身,而无需额外的数据块。这种存储方式具有以下特点:
(1)节省空间:通过在 inode 内部直接存储数据,文件系统避免了分配额外的数据块的需要。这可以节省空间,特别是对于数据量较小的文件。它消除了为这些文件分配和管理单独的数据块的开销。
(2)减少磁盘访问:使用内嵌存储,数据可以直接从 inode 中访问,而无需进行额外的磁盘读取。这可以提高小文件的查找速度,因为数据直接在 inode 结构中可用。
(3)大小限制:内嵌存储有一定的大小限制,因为 inode 具有固定的大小。可以内嵌存储的数据量取决于具体的文件系统及其配置。例如,在 ext4 中,inline_data 选项允许将小文件内嵌存储,通常限制在 60 字节以内。如果文件超过大小限制,则会回退到传统的使用数据块的方式。
(4)使用场景:内嵌存储通常用于存储小文件,例如配置文件、小型脚本或符号链接信息。这些文件通常符合内嵌存储的大小限制,并从其节省空间和提高性能的优势中受益。
Inode 软链接、硬链接
ln -s [path_source] [path_target]
#创建软连接 会在磁盘生成(创建)一个有软连接信息的软连接指向文件,内容记录的是源文件的路径,这个文件有独立的inode号
#软连接可以跨文件系统,可以指向文件或者目录
---------------------------------------------------
ln [path_source] [path_target]
#创建硬链接,硬链接没有独立inode 因此没有独立的datablk块,只是在目标目录数据块文件中中新增了一条文件路径
#硬链接只能指向文件,不能跨文件系统
#创建硬链接后源文件的元信息中的引用计数会+1,删除对应硬链接计数会-1,当计数为0,时表示该文件被删除,对应的blk bitmap 项也会被置0
关于文件的骚操作
df -i path_name
touch path_name/{1..100000}.txt
#创建过多的空文件,使该目录下的inode 号配合全被创建的空文件inode号占满,使得无法在此创建新文件。
#某些特殊文件名的文件使用rm 文件名无法删除时可以使用如下:
ls -li
find -inum [inode_num] -exec rm -i {} /;
文件基本I/O 1-4章、第8章
字符流/文本流/字节流/文件属性/目录/设备文件
Errno
在 Linux 系统中,errno 是一个全局变量,用于表示最近一次系统调用或库函数调用出错时的错误码。每个错误码对应一个特定的错误类型。了解这些错误码有助于调试和处理错误情况。
要使用 errno,需要包含头文件 #include <errno.h>
Linux 系统调用或库函数在出错时会自动设置 errno 为相应的错误码。可以在调用函数后检查 errno 的值来确定错误类型,通过 perror() 或 strerror() 函数将错误码转换为可读的错误消息。
一下为错误示例和正确示例:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main(int argc,char* argvs[]){
...
// 错误示例
if(fsync(fd) == -1){
fprintf(stderr,"fsync fail %s\n",strerror(errno)); // errno 是线程安全的,每一个线程都有一个全局可见的errno,此处fprintf(...) 调用若出现错误则有可能会将原先fsync(...) 调用产生的错误替换掉故打印出来的消息可能不是开发者期待看到的fsync 的errno 对应的错误消息字符串。
if(errno == EIO) fprintf(stderr,"I/O error on %d\n",fd);
}
// 正确示例
if(fsync(fd) == -1){
const err = errno;
fprintf(stderr,"fsync fail %s\n",strerror(err));
if(errno == EIO) fprintf(stderr,"I/O error on %d\n",fd);
exit(EXIT_FAILURE);
}
}
Errno 对照表
在头文件「/usr/include/asm-generic/errno-base.h」中对基础的常用 errno 进行了宏定义:
define | errno | explain |
---|---|---|
EPERM | 1 | Operation not permitted |
ENOENT | 2 | No such file or directory |
ESRCH | 3 | No such process |
EINTR | 4 | Interrupted system call |
EIO | 5 | I/O error |
ENXIO | 6 | No such device or address |
E2BIG | 7 | Argument list too long |
ENOEXEC | 8 | Exec format error |
EBADF | 9 | Bad file number |
ECHILD | 10 | No child processes |
EAGAIN | 11 | Try again |
ENOMEM | 12 | Out of memory |
EACCES | 13 | Permission denied |
EFAULT | 14 | Bad address |
ENOTBLK | 15 | Block device required |
EBUSY | 16 | Device or resource busy |
EEXIST | 17 | File exists |
EXDEV | 18 | Cross-device link |
ENODEV | 19 | No such device |
ENOTDIR | 20 | Not a directory |
EISDIR | 21 | Is a directory |
EINVAL | 22 | Invalid argument |
ENFILE | 23 | File table overflow |
EMFILE | 24 | Too many open files |
ENOTTY | 25 | Not a typewriter |
ETXTBSY | 26 | Text file busy |
EFBIG | 27 | File too large |
ENOSPC | 28 | No space left on device |
ESPIPE | 29 | Illegal seek |
EROFS | 30 | Read-only file system |
EMLINK | 31 | Too many links |
EPIPE | 32 | Broken pipe |
EDOM | 33 | Math argument out of domain of func |
ERANGE | 34 | Math result not representable |
在 「/usr/include/asm-generic/errno.h」 中,对剩余的 errno 做了宏定义:
define | errno | explain |
---|---|---|
EDEADLK | 35 | Resource deadlock would occur |
ENAMETOOLONG | 36 | File name too long |
ENOLCK | 37 | No record locks available |
ENOSYS | 38 | Function not implemented |
ENOTEMPTY | 39 | Directory not empty |
ELOOP | 40 | Too many symbolic links encountered |
EWOULDBLOCK | EAGAIN | Operation would block |
ENOMSG | 42 | No message of desired type |
EIDRM | 43 | Identifier removed |
ECHRNG | 44 | Channel number out of range |
EL2NSYNC | 45 | Level 2 not synchronized |
EL3HLT | 46 | Level 3 halted |
EL3RST | 47 | Level 3 reset |
ELNRNG | 48 | Link number out of range |
EUNATCH | 49 | Protocol driver not attached |
ENOCSI | 50 | No CSI structure available |
EL2HLT | 51 | Level 2 halted |
EBADE | 52 | Invalid exchange |
EBADR | 53 | Invalid request descriptor |
EXFULL | 54 | Exchange full |
ENOANO | 55 | No anode |
EBADRQC | 56 | Invalid request code |
EBADSLT | 57 | Invalid slot |
EDEADLOCK | EDEADLK | |
EBFONT | 59 | Bad font file format |
ENOSTR | 60 | Device not a stream |
ENODATA | 61 | No data available |
ETIME | 62 | Timer expired |
ENOSR | 63 | Out of streams resources |
ENONET | 64 | Machine is not on the network |
ENOPKG | 65 | Package not installed |
EREMOTE | 66 | Object is remote |
ENOLINK | 67 | Link has been severed |
EADV | 68 | Advertise error |
ESRMNT | 69 | Srmount error |
ECOMM | 70 | Communication error on send |
EPROTO | 71 | Protocol error |
EMULTIHOP | 72 | Multihop attempted |
EDOTDOT | 73 | RFS specific error |
EBADMSG | 74 | Not a data message |
EOVERFLOW | 75 | Value too large for defined data type |
ENOTUNIQ | 76 | Name not unique on network |
EBADFD | 77 | File descriptor in bad state |
EREMCHG | 78 | Remote address changed |
ELIBACC | 79 | Can not access a needed shared library |
ELIBBAD | 80 | Accessing a corrupted shared library |
ELIBSCN | 81 | .lib section in a.out corrupted |
ELIBMAX | 82 | Attempting to link in too many shared libraries |
ELIBEXEC | 83 | Cannot exec a shared library directly |
EILSEQ | 84 | Illegal byte sequence |
ERESTART | 85 | Interrupted system call should be restarted |
ESTRPIPE | 86 | Streams pipe error |
EUSERS | 87 | Too many users |
ENOTSOCK | 88 | Socket operation on non-socket |
EDESTADDRREQ | 89 | Destination address required |
EMSGSIZE | 90 | Message too long |
EPROTOTYPE | 91 | Protocol wrong type for socket |
ENOPROTOOPT | 92 | Protocol not available |
EPROTONOSUPPORT | 93 | Protocol not supported |
ESOCKTNOSUPPORT | 94 | Socket type not supported |
EOPNOTSUPP | 95 | Operation not supported on transport endpoint |
EPFNOSUPPORT | 96 | Protocol family not supported |
EAFNOSUPPORT | 97 | Address family not supported by protocol |
EADDRINUSE | 98 | Address already in use |
EADDRNOTAVAIL | 99 | Cannot assign requested address |
ENETDOWN | 100 | Network is down |
ENETUNREACH | 101 | Network is unreachable |
ENETRESET | 102 | Network dropped connection because of reset |
ECONNABORTED | 103 | Software caused connection abort |
ECONNRESET | 104 | Connection reset by peer |
ENOBUFS | 105 | No buffer space available |
EISCONN | 106 | Transport endpoint is already connected |
ENOTCONN | 107 | Transport endpoint is not connected |
ESHUTDOWN | 108 | Cannot send after transport endpoint shutdown |
ETOOMANYREFS | 109 | Too many references: cannot splice |
ETIMEDOUT | 110 | Connection timed out |
ECONNREFUSED | 111 | Connection refused |
EHOSTDOWN | 112 | Host is down |
EHOSTUNREACH | 113 | No route to host |
EALREADY | 114 | Operation already in progress |
EINPROGRESS | 115 | Operation now in progress |
ESTALE | 116 | Stale file handle |
EUCLEAN | 117 | Structure needs cleaning |
ENOTNAM | 118 | Not a XENIX named type file |
ENAVAIL | 119 | No XENIX semaphores available |
EISNAM | 120 | Is a named type file |
EREMOTEIO | 121 | Remote I/O error |
EDQUOT | 122 | Quota exceeded |
ENOMEDIUM | 123 | No medium found |
EMEDIUMTYPE | 124 | Wrong medium type |
ECANCELED | 125 | Operation Canceled |
ENOKEY | 126 | Required key not available |
EKEYEXPIRED | 127 | Key has expired |
EKEYREVOKED | 128 | Key has been revoked |
EKEYREJECTED | 129 | Key was rejected by service |
EOWNERDEAD | 130 | Owner died |
ENOTRECOVERABLE | 131 | State not recoverable |
ERFKILL | 132 | Operation not possible due to RF-kill |
EHWPOISON | 133 | Memory page has hardware error |
字符流/文本流/字节流/文件属性/目录/设备文件
基本文件I/O system calls
概述
在开始基本I/O 函数分享前还是说说Linux中文件描述符的管理方式吧;
- 内核会为每个进程维护一个已打开文件文件表(filetable)。文件表是由一些非负整数进行索引,这些非负整数称为文件描述符(filedescriptors,简称fds)。
- 列表的每一项是一个已打开文件的inode结构体地址。文件描述符使用C语言的int类型表示。这种表示方式正是继承了UNIX传统每个Linux进程可打开的文件数是有上限的。文件描述符的范围从0开始,到上限值减1。默认上限值为1024,但是可以对它进行配置,最大为1048576。因为负数不是合法的文件描述符,函数出错不能返回有效的fd时,通常常会返回-1
- 每个进程默认打开三个文件分别是:STDIN FILENO, STDOUT_FILENO 和STDERR_FILENO
常见Linux C 头文件
- sys/types.h:
- 包含各种基本数据类型的定义,如
size_t
和pid_t
。 - 通常用于定义与系统相关的数据类型。
- 包含各种基本数据类型的定义,如
- sys/stat.h: --- file status
- 包含文件状态信息的结构体定义,如
struct stat
。 - 提供了文件权限位的宏定义,如
S_IRUSR
和S_IWGRP
。 - 用于获取和设置文件的状态信息。
- 包含文件状态信息的结构体定义,如
- fcntl.h:--- file operation conctrl
- 定义了文件控制操作所需的常量,如
O_RDONLY
和O_WRONLY
。 - 提供了
open
函数和一些文件控制操作函数的声明。 - 用于打开、关闭、复制和其他文件操作。
- 定义了文件控制操作所需的常量,如
- unistd.h:
- 提供了 POSIX 系统调用的声明,如
read
、write
和close
。 - 包含一些常用的符号常量,如
STDIN_FILENO
、STDOUT_FILENO
和STDERR_FILENO
。 - 用于实现对文件描述符的基本 I/O 操作。
- 提供了 POSIX 系统调用的声明,如
- errno.h:
- 定义了
errno
全局变量,该变量在系统调用失败时被设置为指示错误的整数值。 - 包含一些与错误码相关的宏定义,如
EACCES
和EINVAL
。 - 用于检查和处理系统调用产生的错误。
- 定义了
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);
flags:
O_RDONLY:只读打开文件 |
---|
O_WRONLY: 只写打开文件 |
O_RDWR: 读写方式打开文件 |
O_CREAT: 如果文件不存在,则创建文件 |
O_EXCL: 与 O_CREAT 一起使用,用于确保文件是新创建的。 如果文件已存在且 O_EXCL 标志被指定,则 open 会失败。 |
O_TRUNC: 如果文件存在且以写方式打开,则将文件截断为零长度 |
O_APPEND: 在文件末尾追加数据 |
O_NONBLOCK: 非阻塞模式。文件描述符将以非阻塞方式打开,读写操作不会阻塞进程 |
O_SYNC: 打开文件以同步写入方式。所有写入操作将立即写入物理介质 |
Tip:前三个方式互斥,但可以与后面的进行或运算
mode
参数用于指定新文件的权限,它是一个三位的八进制数字,每一位表示不同的权限。这个参数通常与 O_CREAT
标志一起使用,以确保新文件被创建时有适当的权限设置。
mode
:
S_IRUSR (0400): 用户(文件所有者)具有读权限 |
---|
S_IWUSR (0200): 用户具有写权限。 |
S_IXUSR (0100): 用户具有执行权限。 |
S_IRGRP (0040): 组具有读权限。 |
S_IWGRP (0020): 组具有写权限。 |
S_IXGRP (0010): 组具有执行权限。 |
S_IROTH (0004): 其他用户具有读权限。 |
S_IWOTH (0002): 其他用户具有写权限。 |
S_IXOTH (0001): 其他用户具有执行权限。 |
这些权限位可以通过按位或运算组合在一起,以设置文件的权限。例如,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
表示文件所有者具有读写权限,而组和其他用户只有读权限。
在
mode
参数中,这些权限位可以使用宏来表示,如S_IRWXU
表示用户有读、写和执行权限。这有助于提高代码的可读性。
create(...)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat (const char *pathname, mode t mode);
creat
系统调用用于以只写方式打开文件,并在文件不存在时创建它。它相当于使用 open
调用,其中包括了 O_WRONLY | O_CREAT | O_TRUNC
标志。
read(...)
#include <unistd.h>
ssize_t read(int fd, void *buf, sizet count);
read(...) 系统调用有6中不同的返回情况见下
return | case |
---|---|
= count | 正常情况,读取期望长度 |
< count | 读缓冲区数据有限,返回值即为这次读取缓冲区所有数据的长度。 或者中间被中断,提前返回。再次read(...) 可能会返回0 |
0 | 没有数据可读或读文本类型文件遇到EOF。 |
-1(非阻塞模式) | errno == EINTR 被信号中断,可重新调用read(...) |
-1(非阻塞模式) | ermo == EAGAIN 没有数据可读,可做其他作业,稍后再次读取 |
-1 | 返回值非 EINTR or EAGAIN, 表示遇到一个更严重的错误,重新read(...)可能不会成功 |
正确read(...) 使用示例见下:
#include <unistd.h>
#include <stdio.h>
int main() {
int fd;
char buf[BUFSIZ];
ssize_t nr;
// ... 假设要读取的文件描述符为fd
// 这里只是示例,实际应用中应根据具体情况获取文件描述符
start:
nr = read(fd, buf, BUFSIZ);
if (nr == -1) {
if (errno == EINTR) {
// 若因信号中断,重新读取
goto start;
} else if (errno == EAGAIN) {
// 遇到EAGAIN,表示暂时没有数据可读,稍后再尝试
printf("EAGAIN encountered. Waiting and retrying...\n");
sleep(1); // 暂停1秒后重新尝试
goto start;
} else {
// 其他错误
perror("read");
return 1;
}
} else if (nr == 0) {
// 到达文件末尾
printf("End of file reached.\n");
} else {
// 成功读取数据,处理读取到的数据...
}
return 0;
}
write(...)
#include <unistd.h>
ssize_t write(int fildes, const void *buf, size_t nbyte);
当write(...) 返回时,说明内核已经把数据从提供的缓冲区拷贝到内核缓冲区中,【在未设置同步直写标志的模式下内核不保证数据已经写入到存储块中。】在后台,内核收集所有这样的 “脏” 缓冲区(即存储的数据比磁盘上的数据新),进行排序优化,然后把这些缓冲区写到磁盘上。
EINTR 阻塞和非阻塞情况说明:
return | casse |
---|---|
= nbytes | normal |
< nbytes (socket 非阻塞) | errno ==EAGAIN 缓冲区已满,只写入了部分数据。同read(...)类似,重新调用write(...) |
< nbytes (local file) | errno ==EFBIG 该文件超过文件系统文件大小限制/写请求被中断 errno == ENOSPC 该文件系统空间满(磁盘没空间) |
0 | 如果errno 没有被这次调用后设置过,表示无异常,有可能写入长度就是0 |
-1 (socket 非阻塞) | errno ==EAGAIN 缓冲去满一个字节也写不下 errno == EINTR 被信号中断,可重新调write(...) |
Tip EINTR :
a、阻塞fd:被一个信号打断,但是需要强调的是,在信号打断前没有写入一个字节,才会返回-1,errno设定为EINTR。如果有写入,返回已经写入的字节数。(os:如果写入了部分数据依然返回-1,errno设定为EINTR,处理完中断后,由于不知道被打断时写到了什么地方,也就不知道该从哪一个地方继续写入。)b、非阻塞fd:调用非阻塞write,即使write被信号打断,write会继续执行未完成的任务而不会去响应信号。因为在非阻塞调用中,没有任何理由阻止read或者wirte的执行。
write注意事项
- 基本操作注意事项
- 返回值判断:write 函数执行成功时,会返回写入的字节数;出错时,返回 -1,并设置 errno 值。
- 部分写情况
- 普通文件:对于普通文件,除非发生错误,write 操作保证会执行整个写请求。通常不需要执行循环写操作,但对于其他文件类型,如 socket,可能需要循环来保证写了所有请求的字节。
- 错误处理:当 write 操作返回 -1 时,需要根据 errno 的值进行错误处理。常见的 errno 值包括 EBADF、EFAULT、EFBIG、EINVAL、EIO、ENOSPC、EPIPE 等。
- 特殊模式注意事项
- Append 模式:当以 Append 模式(参数设置 O_APPEND)打开文件描述符时,写操作从当前文件的末尾开始,保证了文件位置指针总是指向文件末尾,避免了多个写进程之间的竞争问题。
- 非阻塞写:以非阻塞模式(参数设置 O_NONBLOCK)打开文件,当发起写操作时,系统调用 write 可能会返回 -1,并设置 errno 值为 EAGAIN。在这种情况下,请求可以稍后重新发起。
- 数据一致性注意事项
- 延迟写问题
- 数据同步:write 调用执行非常快,数据被拷贝到缓冲区后,内核会在后台将缓冲区的数据写到磁盘上。但这可能导致数据在系统崩溃时没有写入磁盘,存在数据丢失的风险。
- 同步操作:为了保证数据按时写入磁盘,可以使用 fsync、fdatasync 等函数进行同步操作。
- 延迟写问题
- 其他注意事项
- 参数限制
- count 值:如果 count 值大于 SSIZE_MAX,调用 write 的结果是未定义的。
- 写入零字节:调用 write 时,如果 count 值为零,会立即返回,且返回值为 0。
- 参数限制
正确write(...)见下:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int main() {
int fd;
const char *data = "Hello, World!";
ssize_t numBytesWritten;
// 打开一个文件用于写入,如果文件不存在则创建
fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
do {
numBytesWritten = write(fd, data, strlen(data));
if (numBytesWritten == -1) {
if (errno == EINTR) {
// 处理信号中断错误,继续尝试
continue;
} else if (errno == EAGAIN) {
// 处理暂时无法写入的情况,例如缓冲区已满或I/O资源不可用
// 可以选择等待一段时间后再次尝试,或者根据具体情况进行相应的处理
sleep(1);
continue;
} else {
// 处理其他错误
perror("write");
break;
}
} else if (numBytesWritten < strlen(data)) {
// 处理部分写入的情况
perror("Partial write");
break;
}
} while (numBytesWritten < strlen(data));
// 关闭文件描述符
if (close(fd) == -1) {
perror("close");
exit(1);
}
if (numBytesWritten == strlen(data)) {
printf("成功写入 %ld 个字节到文件\n", (long)numBytesWritten);
} else {
// 可以根据具体需求进行错误处理,例如提示错误信息或采取其他恢复措施
printf("写入失败\n");
}
return 0;
}