Ⅰ. open/close/read/write 打开/关闭/读/写
#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);
- 返回值:成功返回新分配的文件描述符,出错返回-1并设置errno
- pathname参数是要打开或创建的文件名,既可以是绝对路径也可以是相对路径
- flags参数有一系列常数值可供选择,可以同时选择多个常数用按位或运算符连接起来,所以这些常数的宏定义都以O_开头,表示or
- 必选项:以下三个常数中必须指定一个,且仅允许指定一个。
- O_RDONLY:只读打开
- O_WRONLY:只写打开
- O_RDWR:可读可写打开
- 可选项:可以同时指定0个或多个,和必选项按位或起来作为flags参数。
- O_CREAT:若此文件不存在则创建它。必须要提供第三个参数mode,表示文件的访问权限。
- O_EXCL:如果同时指定了O_CREAT,并且文件已存在,则返回错误。
- O_TRUNC:如果文件已存在,并且以只读或可读可写方式打开,则将其长度截断为0字节。
- O_APPEND:表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾。
- O_NONBLOCK:对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O。
- 必选项:以下三个常数中必须指定一个,且仅允许指定一个。
- mode参数指定文件权限,可以用八进制数表示,比如0644表示
-rw-r--r--
,也可以用 S_IRUSR、S_IWUSR 等宏定义按位或起来表示。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("usage:cmd filename\n");
return -1;
}
// int fd = open(argv[1], O_WRONLY | O_CREAT, 0644); //w+,写文件,如果文件不存在则创建。成功返回新分配的文件描述符,出错返回-1并设置errno
// int fd = = open(argv[1], O_WRONLY | O_CREAT | O_EXCL, 0644); // 写文件,如果文件不存在则创建,如果文件存在则返回-1并设置errno
int fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644); // 写文件,如果文件存在则截断为0字节,如果文件不存在则创建它
if (fd < 0) {
perror("OPEN"); // perror作用:翻译错误码errno为对应的描述
exit(-1);
}
return 0;
}
#include <unistd.h>
int close(int fd);
- 返回值:成功返回 0, 出错返回-1并设置 erno
- 参数 fd 是要关闭的文件描述符。当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用 close 关闭,所以即使用户程序不调用 close,在终止时内核也会自动关闭它打开的所有文件。
由 open 返回的文件描述符一定是该进程尚未使用的最小描述符。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
if (argc < 4) {
printf("usage:cmd filename1 filename2 filename3\n");
return -1;
}
// 测试open返回的文件描述符一定是该进程尚未使用的最小描述符
// 思路:打开文件1,打开文件2,关闭文件1,打开文件3,关闭文件2,关闭文件3.输出文件描述符fd
int fd1 = open(argv[1], O_WRONLY | O_CREAT, 0644);
if (fd1 < 0) {
perror("OPEN1");
exit(-1);
} else {
printf("fd1=%d\n", fd1);
}
int fd2 = open(argv[2], O_WRONLY | O_CREAT, 0644);
if (fd2 < 0) {
perror("OPEN2");
exit(-2);
} else {
printf("fd2=%d\n", fd2);
}
close(fd1);
int fd3 = open(argv[3], O_WRONLY | O_CREAT, 0644);
if (fd3 < 0) {
perror("OPEN3");
exit(-3);
} else {
printf("fd3=%d\n", fd3);
}
close(fd2);
close(fd3);
return 0;
// 输出结果:
// fd1=3
// fd2=4
// fd3=3
}
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
- 返回值:成功返回读取的字节数,出错返回-1 并设置 errno,如果在调 read 之前已到达文件末尾,则这次 read 返回 0
- 参数 count 是请求读取的字节数,读上来的数据保存在缓冲区 buf 中,同时文件的当前读写位置向后移。注意这个读写位置和使用C标准/0库时的读写位置有可能不同,这个读写位置是记在内核中的,而使用C标准I/0库时的读写位置是用户空间I/0缓冲区中的位置。
有些情况下,实际读到的字节数(返回值)会小于请求读的字节数 count,例如:- 读常规文件时,在读到 count 个字节之前已到达文件末尾。
- 从终端设备读,通常以行为单位,读到换行符就返回了。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("usage:cmd filename\n");
return -1;
}
// 准备一个内容小于20个字符的文件,用来测试读到count个字节之前已到达文件末尾时会返回的情况
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("OPEN");
exit(-1);
} else {
printf("fd=%d\n", fd);
}
char buf[20];
ssize_t n;
n = read(fd, buf, 10);
printf("read %ld bytes\n", n);
for (int i = 0; i < n; i++) {
printf("%c", buf[i]);
}
printf("\n");
n = read(fd, buf, 10);
printf("read %ld bytes\n", n);
for (int i = 0; i < n; i++) {
printf("%c", buf[i]);
}
printf("\n");
close(fd);
return 0;
}
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
char buf[20];
ssize_t n;
n = read(STDIN_FILENO, buf, 10); // 从标准输入中读取,以行为单位,读到换行符就会返回
if (n < 0) {
perror("READ STDIN");
exit(-1);
}
printf("read %ld bytes\n", n);
for (int i = 0; i < n; i++) {
printf("%c", buf[i]);
}
putchar(10); // 相当于printf("\n")
return 0;
}
#include <unistd.h>
ssite_t write(int fd, const void *buf, size_t count);
- 返回值:成功返回写入的字节数,出错返回-1并设置errno
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
char buf[20];
ssize_t n;
n = read(STDIN_FILENO, buf, 10); // 从标准输入中读取
if (n < 0) {
perror("READ STDIN");
exit(-1);
}
printf("read %ld bytes\n", n);
write(STDOUT_FILENO, buf, n);
write(STDOUT_FILENO, "\n", 1);
return 0;
}
Ⅱ. 阻塞与非阻塞
当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用 sleep 指定的睡眠时间到了)它才有可能继续运行。
与睡眠状态相对的是运行(Running)状态,在 Linux 内核中,处于运行状态的进程分为两种情况:
- 正在被调度执行:GPU 处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,正在执行该进程的指令,正在读写该进程的地址空间。
- 就绪状态:该进程不需要等待什么事件发生,随时都可以执行,但 CPU 暂时还在执行另个进程,所以该进程在一个就绪队列中等待被内核调度。
如果在 openー个设备时指定了 O_NONBLOCK 标志,read/ write 就不会阻塞。以 read 为例,如果设备暂时没有数据可读就返回-1, 同时置 errno为 EWOULDBL0K(或者 EAGAIN,这两个宏定义的值相同),表示本来应该阻塞在这里(would block,虚拟语气),事实上并没有阻塞而是直接返回错误,调用者应该试着再读一次(again)。这种行为方式称为轮询(Poll),调用者只是查询下,而不是阻塞在这里死等,这样可以同时监视多个设备:
while(1){
非阻塞read(设备1);
if(设备1有数据到达)
处理数据
非阻塞read(设备2);
if(设备2有数据到达)
处理数据
...
sleep(n);
}
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
int fd = open("/dev/tty", O_RDONLY | O_NONBLOCK); // 从终端上非阻塞地读
if (fd < 0) {
perror("OPEN /dev/tty");
exit(-1);
}
char buf[10];
while (1) { // 轮询读
ssize_t n = read(fd, buf, 10);
if (n > -1) {
// 读成功了
printf("read %ld bytes\n", n);
write(STDOUT_FILENO, buf, n);
putchar(10);
break;
}
if (errno != EAGAIN) {
// 报错了
perror("READ /dev/tty");
exit(-2);
}
//errno == EAGAIN, 暂时没有数据可读
write(STDOUT_FILENO, "try try?\n", 9);
sleep(1);
}
close(fd);
return 0;
}
评论 (0)