目錄
一、read?函數
1.1. 函數原型
1.2. 參數說明
1.3. 返回值
1.4. 示例代碼
二、write?函數
2.1. 函數原型
2.2. 參數說明
2.3. 返回值
2.4. 示例代碼
三、關鍵注意事項
3.1 部分讀寫
3.2 錯誤處理
3.3 阻塞與非阻塞模式
3.4 數據持久化
3.5 線程安全
四、嵌入式場景應用
4.1. 文件數據讀寫
4.2. 設備驅動交互
4.3. 進程間通信(IPC)
4.4. 網絡通信
五、常見問題
5.1. read函數常見問題
5.2. write函數常見問題
5.3 通用建議
六、總結
在嵌入式Linux應用開發中,read
和write
函數是文件I/O操作中最基礎、最常用的兩個系統調用。它們用于從文件描述符(file descriptor)指向的文件或設備中讀取數據和向其中寫入數據。
一、read
?函數
1.1. 函數原型
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
1.2. 參數說明
fd
:文件描述符,由?open
?函數返回的一個非負整數,用于標識要讀取數據的文件、設備等。buf
:指向用于存儲讀取數據的緩沖區的指針,數據將被讀取到這個緩沖區中。count
:期望讀取的字節數,即希望從文件描述符對應的文件或設備中讀取的最大字節數。
1.3. 返回值
- 大于 0:表示實際成功讀取的字節數。
- 等于 0:表示已經到達文件末尾(EOF),沒有更多數據可供讀取。
- 等于 -1:表示讀取操作失敗,此時?
errno
?會被設置為相應的錯誤碼,用于指示具體的錯誤原因。
1.4. 示例代碼
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main() {int fd = open("example.txt", O_RDONLY);if (fd == -1) {perror("open");return 1;}char buffer[100];ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);if (bytesRead == -1) {perror("read");close(fd);return 1;}buffer[bytesRead] = '\0'; // 確保字符串以NULL結尾printf("Read %zd bytes: %s\n", bytesRead, buffer);close(fd);return 0;
}
二、write
?函數
2.1. 函數原型
#include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);
2.2. 參數說明
fd
:文件描述符,標識要寫入數據的文件、設備等。buf
:指向包含要寫入數據的緩沖區的指針。count
:要寫入的字節數,即希望將緩沖區中多少字節的數據寫入到文件描述符對應的文件或設備中。
2.3. 返回值
- 大于 0:表示實際成功寫入的字節數。
- 等于 0:通常表示沒有寫入任何數據,可能是由于某些特殊情況(如文件系統已滿但還未返回錯誤)。
- 等于 -1:表示寫入操作失敗,
errno
?會被設置為相應的錯誤碼,用于指示具體的錯誤原因。
2.4. 示例代碼
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main() {int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (fd == -1) {perror("open");return 1;}const char *message = "Hello, World!\n";ssize_t bytesWritten = write(fd, message, strlen(message));if (bytesWritten == -1) {perror("write");close(fd);return 1;}printf("Written %zd bytes\n", bytesWritten);close(fd);return 0;
}
三、關鍵注意事項
3.1 部分讀寫
-
原因:數據未就緒(如網絡)、資源限制(如管道緩沖區滿)、信號中斷等。
-
處理方式:循環調用函數,直至完成全部數據傳輸。
示例代碼(讀操作):
ssize_t total_read = 0;
while (total_read < count) {ssize_t n = read(fd, buf + total_read, count - total_read);if (n == 0) break; // EOFif (n < 0 && errno != EINTR) break; // 非中斷錯誤if (n > 0) total_read += n;
}
3.2 錯誤處理
-
常見
errno
值:-
EAGAIN
/EWOULDBLOCK
:非阻塞模式下無數據可讀或寫緩沖區滿。 -
EINTR
:操作被信號中斷。 -
EBADF
:無效文件描述符。
-
-
處理建議:
-
對
EINTR
需重試操作。 -
對非阻塞I/O的
EAGAIN
需結合select
/poll
等待就緒。
-
3.3 阻塞與非阻塞模式
-
設置非阻塞模式:
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
3.4 數據持久化
-
立即同步:調用
fsync(fd)
強制將內核緩沖區數據寫入存儲設備。
3.5 線程安全
-
多線程操作同一文件描述符需加鎖(如
pthread_mutex
)。
四、嵌入式場景應用
4.1. 文件數據讀寫
①配置文件讀取
-
場景:嵌入式系統中的應用程序常常需要從配置文件中讀取參數,以此來初始化系統。例如,網絡設備的配置文件包含 IP 地址、子網掩碼、網關等信息,應用程序需要讀取這些信息來完成網絡配置。
-
代碼示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#define BUFFER_SIZE 1024int main() {int fd = open("config.txt", O_RDONLY);if (fd == -1) {perror("open");return 1;}char buffer[BUFFER_SIZE];ssize_t bytes_read = read(fd, buffer, BUFFER_SIZE);if (bytes_read == -1) {perror("read");} else if (bytes_read > 0) {buffer[bytes_read] = '\0';// 處理讀取到的配置信息}close(fd);return 0;
}
?②數據文件寫入
- 場景:在數據采集系統中,需要將采集到的數據存儲到文件中,以便后續分析和處理。比如,溫度傳感器每隔一段時間采集一次溫度數據,應用程序將這些數據寫入到文件中。
- 代碼示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>#define DATA "25.5"int main() {int fd = open("data.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);if (fd == -1) {perror("open");return 1;}ssize_t bytes_written = write(fd, DATA, strlen(DATA));if (bytes_written == -1) {perror("write");}close(fd);return 0;
}
4.2. 設備驅動交互
①傳感器數據讀取
- 場景:嵌入式系統通常會連接各種傳感器,如加速度計、陀螺儀等。應用程序通過?
read
?函數從相應的設備文件中讀取傳感器數據。 - 代碼示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>#define SENSOR_DEVICE "/dev/sensor"
#define BUFFER_SIZE 32int main() {int fd = open(SENSOR_DEVICE, O_RDONLY);if (fd == -1) {perror("open");return 1;}char buffer[BUFFER_SIZE];ssize_t bytes_read = read(fd, buffer, BUFFER_SIZE);if (bytes_read == -1) {perror("read");} else if (bytes_read > 0) {buffer[bytes_read] = '\0';// 處理傳感器數據}close(fd);return 0;
}
②設備控制命令寫入
- 場景:對于一些可控制的設備,如 LED 燈、電機等,應用程序可以通過?
write
?函數向設備文件寫入控制命令,從而實現對設備的控制。 - 代碼示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>#define LED_DEVICE "/dev/led"
#define COMMAND "ON"int main() {int fd = open(LED_DEVICE, O_WRONLY);if (fd == -1) {perror("open");return 1;}ssize_t bytes_written = write(fd, COMMAND, strlen(COMMAND));if (bytes_written == -1) {perror("write");}close(fd);return 0;
}
③串口/UART通信
串口設備(如/dev/ttyS0
)是嵌入式系統中常見的通信接口,read
和write
用于收發數據:
// 配置串口后...
char tx_data[] = "Hello UART!";
write(uart_fd, tx_data, strlen(tx_data)); // 發送數據char rx_data[32];
ssize_t len = read(uart_fd, rx_data, sizeof(rx_data)); // 接收數據
4.3. 進程間通信(IPC)
①管道通信
- 場景:在嵌入式系統中,不同進程之間可能需要進行數據交換。管道是一種簡單的進程間通信方式,一個進程通過?
write
?函數向管道寫入數據,另一個進程通過?read
?函數從管道讀取數據。 - 代碼示例
#include <stdio.h>
#include <unistd.h>
#include <string.h>#define BUFFER_SIZE 1024int main() {int pipefd[2];if (pipe(pipefd) == -1) {perror("pipe");return 1;}pid_t pid = fork();if (pid == -1) {perror("fork");return 1;}if (pid == 0) {// 子進程:讀取數據close(pipefd[1]);char buffer[BUFFER_SIZE];ssize_t bytes_read = read(pipefd[0], buffer, BUFFER_SIZE);if (bytes_read == -1) {perror("read");} else if (bytes_read > 0) {buffer[bytes_read] = '\0';printf("Child process read: %s\n", buffer);}close(pipefd[0]);} else {// 父進程:寫入數據close(pipefd[0]);const char *message = "Hello from parent!";ssize_t bytes_written = write(pipefd[1], message, strlen(message));if (bytes_written == -1) {perror("write");}close(pipefd[1]);}return 0;
}
4.4. 網絡通信
套接字數據讀寫
- 場景:在嵌入式網絡應用中,通過套接字進行網絡通信時,使用?
read
?函數接收網絡數據,使用?write
?函數發送網絡數據。 - 代碼示例(簡單 TCP 客戶端)
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) {perror("socket");return 1;}struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("connect");close(sockfd);return 1;}const char *message = "Hello, server!";ssize_t bytes_written = write(sockfd, message, strlen(message));if (bytes_written == -1) {perror("write");}char buffer[BUFFER_SIZE];ssize_t bytes_read = read(sockfd, buffer, BUFFER_SIZE);if (bytes_read == -1) {perror("read");} else if (bytes_read > 0) {buffer[bytes_read] = '\0';printf("Received from server: %s\n", buffer);}close(sockfd);return 0;
}
五、常見問題
5.1. read
函數常見問題
①讀取到的字節數少于請求數:
- 原因:
- 讀普通文件時,在讀到請求字節數之前已到達文件尾端。
- 從終端設備讀時,通常一次最多讀一行。
- 從網絡讀時,網絡中的緩沖機構可能造成返回值小于請求讀的字節數。
- 某些面向記錄的設備(如磁帶),一次最多返回一個記錄。
- 解決方案:
- 在讀取文件時,需要檢查返回值是否小于請求字節數,并處理文件尾端的情況。
- 對于從終端設備或網絡讀取的數據,需要采用適當的緩沖機制來處理數據。
②讀取操作失敗:
- 原因:
- 文件描述符無效或沒有讀權限。
- 提供的緩沖區指針無效。
- 文件已被其他進程鎖定或刪除。
- 解決方案:
- 確保文件描述符有效且具有讀權限。
- 檢查緩沖區指針的有效性。
- 使用文件鎖或其他同步機制來避免文件被其他進程鎖定或刪除。
③讀取的數據不準確:
- 原因:
- 文件指針未正確設置。
- 文件內容在讀取過程中被其他進程修改。
- 解決方案:
- 在讀取文件之前,確保文件指針已正確設置到所需的位置。
- 使用文件鎖或其他同步機制來避免文件內容在讀取過程中被其他進程修改。
5.2. write
函數常見問題
①寫入操作失敗:
- 原因:
- 文件描述符無效或沒有寫權限。
- 磁盤已滿或文件系統已滿。
- 提供的緩沖區指針無效。
- 解決方案:
- 確保文件描述符有效且具有寫權限。
- 檢查磁盤和文件系統的剩余空間。
- 檢查緩沖區指針的有效性。
②寫入的字節數少于請求數:
- 原因:
- 磁盤已滿或文件系統限制導致無法寫入更多數據。
- 網絡或設備緩沖區已滿,導致寫入操作被阻塞或提前返回。
- 解決方案:
- 在寫入文件之前,檢查磁盤和文件系統的剩余空間。
- 對于網絡或設備寫入操作,需要采用適當的緩沖機制和重試策略來處理寫入失敗的情況。
③寫入的數據未立即生效:
- 原因:數據被寫入內核緩沖區,而尚未被刷新到磁盤。
- 解決方案:使用
fsync
或fdatasync
函數來強制將緩沖區中的數據同步到磁盤。
5.3 通用建議
- 錯誤處理:
- 在使用
read
和write
函數時,務必檢查返回值,并根據返回值進行相應的錯誤處理。 - 可以使用
errno
變量來獲取更詳細的錯誤信息。
- 在使用
- 資源管理:
- 在使用文件描述符時,要確保在不再需要時關閉它們,以釋放系統資源。
- 對于網絡套接字或其他資源,也需要進行適當的資源管理和釋放。
- 同步與并發:
- 在多進程或多線程環境中,需要確保對文件或其他資源的訪問是同步的,以避免數據競爭和不一致性。
- 可以使用文件鎖、信號量或其他同步機制來實現這一點。
六、總結
read
和write
函數是嵌入式Linux應用開發中用于文件I/O操作的基礎工具。通過這兩個函數,可以實現從文件或設備讀取數據和向文件或設備寫入數據。了解并正確使用這些函數,對于開發穩定、高效的嵌入式應用程序至關重要。