1. 函数介绍
* pwrite
: 将 count
个字节的数据从缓冲区 buf
写入到文件描述符 fd
关联的文件中,从指定的偏移量 offset
处开始写入。关键点:此操作也不会修改文件的当前读写位置指针。
你可以把它们想象成 lseek
+ read
或 lseek
+ write
的原子性组合,但又不影响文件的“书签”(当前文件偏移量)。
2. 函数原型
#include <unistd.h> // 必需
// 从指定偏移量读取
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
// 向指定偏移量写入
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
3. 功能
pread(fd, buf, count, offset)
:- 将文件
fd
的读取位置临时设置到offset
。 - 从该位置读取最多
count
个字节到buf
。 - 读取完成后,文件的全局读写位置指针保持不变。
- 将文件
pwrite(fd, buf, count, offset)
:- 将文件
fd
的写入位置临时设置到offset
。 - 从
buf
写入count
个字节到该位置。 - 写入完成后,文件的全局读写位置指针保持不变。
- 将文件
这种“原子性定位并操作”的特性在多线程环境中特别有用,可以避免多个线程同时操作同一个文件描述符的当前偏移量而导致的竞争条件(race condition)。
4. 参数
这两个函数的参数非常相似:
int fd
: 有效的文件描述符。void *buf
(pread
) /const void *buf
(pwrite
): 指向数据缓冲区的指针。size_t count
: 要读取/写入的字节数。off_t offset
: 在文件中进行读取/写入操作的绝对偏移量(从文件开头算起的字节数)。
5. 返回值
- 成功时:
- 返回实际读取/写入的字节数。这个数可能小于请求的
count
(例如,在读取时接近文件末尾,或在写入时遇到磁盘空间不足)。 - 对于
pread
,如果返回 0,通常表示偏移量已在文件末尾或没有更多数据。
- 返回实际读取/写入的字节数。这个数可能小于请求的
- 失败时:
- 返回 -1,并设置全局变量
errno
来指示具体的错误原因(例如EBADF
fd
无效,EINVAL
offset
无效,EIO
I/O 错误等)。
- 返回 -1,并设置全局变量
6. 相似函数,或关联函数
read
,write
: 基础的读写函数,它们的操作基于并会修改文件的当前偏移量。lseek
: 用于显式地移动文件的当前读写位置指针。pread
/pwrite
内部可能使用了类似lseek
的机制,但对用户是透明的,且不影响全局偏移量。mmap
: 另一种访问文件内容的方式,通过内存映射将文件内容映射到进程地址空间。
7. 示例代码
示例 1:基本 pread
和 pwrite
使用
这个例子演示了如何使用 pread
从文件的不同位置读取数据,以及使用 pwrite
向文件的不同位置写入数据,同时文件的当前偏移量保持不变。
#include <unistd.h> // pread, pwrite, open, close, lseek
#include <fcntl.h> // O_RDWR, O_CREAT
#include <stdio.h> // perror, printf
#include <stdlib.h> // exit
#include <string.h> // strlen
int main() {
int fd;
const char *filename = "pread_pwrite_example.txt";
const char *initial_data = "This is the initial content of the file.\nIt spans multiple lines.\n";
const char *write_data1 = "[OVERWRITTEN_PART_1]";
const char *write_data2 = "[OVERWRITTEN_PART_2]";
char read_buffer[100];
ssize_t bytes_rw;
off_t current_offset;
// 1. 创建并写入初始数据
fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open for creation");
exit(EXIT_FAILURE);
}
if (write(fd, initial_data, strlen(initial_data)) == -1) {
perror("write initial data");
close(fd);
exit(EXIT_FAILURE);
}
printf("Created file '%s' and wrote initial data.\n", filename);
// 2. 获取并打印当前文件偏移量 (应在文件末尾)
current_offset = lseek(fd, 0, SEEK_CUR);
if (current_offset == -1) {
perror("lseek to get current offset");
close(fd);
exit(EXIT_FAILURE);
}
printf("Current file offset after initial write: %ld\n", (long)current_offset);
// --- 使用 pwrite 进行写入 ---
printf("\n--- Using pwrite ---\n");
// 在偏移量 5 处写入数据
bytes_rw = pwrite(fd, write_data1, strlen(write_data1), 5);
if (bytes_rw == -1) {
perror("pwrite 1");
close(fd);
exit(EXIT_FAILURE);
}
printf("pwrite 1: Wrote %zd bytes at offset 5.\n", bytes_rw);
// 在偏移量 30 处写入另一部分数据
bytes_rw = pwrite(fd, write_data2, strlen(write_data2), 30);
if (bytes_rw == -1) {
perror("pwrite 2");
close(fd);
exit(EXIT_FAILURE);
}
printf("pwrite 2: Wrote %zd bytes at offset 30.\n", bytes_rw);
// 3. 再次检查当前文件偏移量 (应该没有改变)
off_t offset_after_pwrite = lseek(fd, 0, SEEK_CUR);
if (offset_after_pwrite == -1) {
perror("lseek after pwrite");
close(fd);
exit(EXIT_FAILURE);
}
printf("Current file offset after pwrite calls: %ld (Should be same as before)\n",
(long)offset_after_pwrite);
// --- 使用 pread 进行读取 ---
printf("\n--- Using pread ---\n");
// 从偏移量 0 开始读取 20 个字节
bytes_rw = pread(fd, read_buffer, 20, 0);
if (bytes_rw == -1) {
perror("pread 1");
close(fd);
exit(EXIT_FAILURE);
}
read_buffer[bytes_rw] = '\0'; // 确保字符串结束
printf("pread 1: Read %zd bytes from offset 0: '%s'\n", bytes_rw, read_buffer);
// 从偏移量 15 开始读取 25 个字节
bytes_rw = pread(fd, read_buffer, 25, 15);
if (bytes_rw == -1) {
perror("pread 2");
close(fd);
exit(EXIT_FAILURE);
}
read_buffer[bytes_rw] = '\0';
printf("pread 2: Read %zd bytes from offset 15: '%s'\n", bytes_rw, read_buffer);
// 从偏移量 50 开始读取 10 个字节 (可能读到文件末尾)
bytes_rw = pread(fd, read_buffer, 10, 50);
if (bytes_rw == -1) {
perror("pread 3");
close(fd);
exit(EXIT_FAILURE);
} else if (bytes_rw == 0) {
printf("pread 3: Read %zd bytes from offset 50 (likely EOF).\n", bytes_rw);
} else {
read_buffer[bytes_rw] = '\0';
printf("pread 3: Read %zd bytes from offset 50: '%s'\n", bytes_rw, read_buffer);
}
// 4. 最后再次确认文件偏移量未变
off_t final_offset = lseek(fd, 0, SEEK_CUR);
if (final_offset == -1) {
perror("lseek to get final offset");
close(fd);
exit(EXIT_FAILURE);
}
printf("\nFinal file offset: %ld (Should still be the same)\n", (long)final_offset);
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
printf("File operations completed. Check the file content.\n");
return 0;
}
代码解释:
- 创建一个文件并写入一些初始数据。
- 使用
lseek(fd, 0, SEEK_CUR)
获取并打印当前文件偏移量(应该在文件末尾)。 pwrite
操作:- 调用
pwrite
两次,分别在偏移量 5 和 30 处写入数据。 - 每次调用后,再次使用
lseek
检查文件偏移量,确认它没有改变。
- 调用
pread
操作:- 调用
pread
三次,分别从偏移量 0、15 和 50 处读取数据。 - 打印读取到的内容。
- 调用
- 最后再次检查文件偏移量,确认在整个过程中它始终保持不变。
- 关闭文件。
示例 2:多线程环境中的 pread
/pwrite
这个例子(概念性地)说明了 pread
/pwrite
在多线程场景下的优势。虽然完整的多线程代码比较复杂,但我们可以通过伪代码和解释来理解。
// 假想的多线程程序片段
#include <pthread.h> // POSIX 线程
#include <unistd.h> // pread, pwrite
// ... 其他包含 ...
int shared_file_fd; // 所有线程共享的文件描述符
// 线程函数 1: 读取文件头部
void* thread_read_header(void *arg) {
char header_buf[HEADER_SIZE];
ssize_t bytes_read;
// 线程 1 总是从偏移量 0 读取头部
// 使用 pread 确保不影响其他线程的文件位置
bytes_read = pread(shared_file_fd, header_buf, HEADER_SIZE, 0);
if (bytes_read > 0) {
// 处理头部数据...
process_header(header_buf, bytes_read);
}
return NULL;
}
// 线程函数 2: 读取文件尾部
void* thread_read_footer(void *arg) {
char footer_buf[FOOTER_SIZE];
ssize_t bytes_read;
off_t file_size;
// 获取文件大小 (可能需要预先获取或用 fstat)
file_size = get_file_size_somehow(shared_file_fd);
// 线程 2 总是从文件末尾倒数的位置读取尾部
// 使用 pread 确保不影响其他线程的文件位置
bytes_read = pread(shared_file_fd, footer_buf, FOOTER_SIZE, file_size - FOOTER_SIZE);
if (bytes_read > 0) {
// 处理尾部数据...
process_footer(footer_buf, bytes_read);
}
return NULL;
}
// 线程函数 3: 在文件中间某处写入日志
void* thread_write_log(void *arg) {
const char *log_msg = "Log entry from thread 3\n";
off_t write_offset = (off_t)arg; // 假设偏移量通过 arg 传入
// 线程 3 在指定位置写入日志
// 使用 pwrite 确保不影响其他线程的文件位置
ssize_t bytes_written = pwrite(shared_file_fd, log_msg, strlen(log_msg), write_offset);
if (bytes_written == -1) {
perror("pwrite in thread 3");
} else {
printf("Thread 3 wrote %zd bytes at offset %ld\n", bytes_written, (long)write_offset);
}
return NULL;
}
// 主函数 (概念性)
int main() {
// ... 打开文件 shared_file_fd ...
pthread_t t1, t2, t3;
// 创建线程
pthread_create(&t1, NULL, thread_read_header, NULL);
pthread_create(&t2, NULL, thread_read_footer, NULL);
pthread_create(&t3, NULL, thread_write_log, (void*)MIDDLE_OFFSET); // 传递写入偏移量
// 等待线程完成
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
// ... 关闭文件 ...
return 0;
}
代码解释 (概念性):
总结: