1.open函数
可以打开或创建一个文件。
返回值:成功返回新分配的文件描述符,出错返回-1并设置errno
pathname:打开或创建的文件名,是相对路径or绝对路径。
flags:可同时选择多个常数用按位或运算符连接,宏定义都是以O_开头,表示or。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.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);
if (fd < 0) {
perror("OPEN");
return -1;
}
return 0;
}
2.close函数
关闭一个已打开的文件。
fd:关闭的文件描述符。当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。
由open返回的文件描述符一定是该进程尚未使用的最小描述符。
3.read函数
从打开的设备或文件中读取数据。
count:请求读取的字节数,读上来的数据保存在缓冲区,同时文件的当前读写位置向后移。
注意:这个读写位置和使用C标准I/O库时的读写位置可能不同,这个读写位置记在内核中,而使用C标准I/O库时读写位置是用户空间I/O缓冲区位置。
有些情况,实际读到字节数会小于请求读的字节数count。如:
①读常规文件时,在读到count个字节之前已达到文件末尾。
②从终端设备读,通常以行为单位,读到换行符就返回了。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
char buf[20];
int n = read(STDIN_FILENO, buf, 10);
if (n < 0) {
perror("READ STDIN");
exit(1);
}
printf("read %d bytes\n", n);
for (int i = 0; i < n; i++) {
printf("%c", buf[i]);
}
putchar(10);
close(n);
return 0;
}
4.write函数
向打开的设置或文件中写数据
5.阻塞与非阻塞
阻塞:当进程调用一个阻塞的系统函数时,该进程被置于Sleep状态,这时内核调度其他进程运行,直到该进程等待的事件发生了(如:网络上接收到数据包,调用sleep指定的睡眠时间到)才有可能继续运行。
sleep相对是running状态。在linux内核中,处于运行状态的进程分为两种情况:
①正在被调度执行:CPU处于该进程的上下文环境中,程序计数器(eip)保存着该进程的指令地址,正在执行该进程的指令,正在读写该进程的地址空间。
②就绪状态:该进程不需要等待什么事情发生,随时都可执行,但CPU暂时还在执行另一个进程,所有该进程在一个就绪队列中等待被内核调度。
如果在open一个设置时指定O_NONBLOCK标志,read/write就不会阻塞。
以read为例,如果设备暂时没有数据可读就返回-1,通知errno为EWOULDBLOCK(或者EAGAIN),表示本该阻塞在这里,事实上并没有阻塞而是直接返回错误,调用者应该试着再读一次,称为轮询。
调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
int fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
if (fd < 0) {
perror("OPEN /dev/tty");
exit(1);
}
int n;
char buf[10];
int cnt=10;
while (cnt>0) {
n = read(fd, buf, 10);
if (~n) {
printf("read %d bytes\n", n);
write(STDOUT_FILENO, buf, n);
break;
}
if (errno != EAGAIN) {
perror("READ /dev/tty");
exit(1);
}
write(STDOUT_FILENO, "try try?\n", 9);
sleep(1);
cnt--;
}
close(fd);
return 0;
}
6.Iseek
每个打开的文件都记录着当前读写位置,打开文件时读写位置是0,表示文件开头,通常读写多少个字节就会将读写位置往后移多少个字节。
除了O_APPEND打开,每次写操作都会在文件末尾追加数据,然后将读写位置移动新的文件末尾。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
int fd;
if ((fd=open("./test.txt", O_RDONLY)) < 0) {
perror("OPEN");
exit(1);
}
char c;
read(fd, &c, 1);
write(STDOUT_FILENO, &c, 1);
int pos = lseek(fd, 3, SEEK_CUR);
read(fd, &c, 1);
write(STDOUT_FILENO, &c, 1);
printf("\npos=%d\n", pos);
close(fd);
return 0;
}
7.fcntl
用fcntl函数改变一个已打开的文件的属性而不必重新open文件,可以重新设置读、写、追加、非阻塞等标志。
除了F_GETFL,F_SETFL命令之外,fcntl还有哈恩多命令做其他操作,如:设置文件记录锁。
可以通过fcntl设置的都是当前进程如何访问设备或文件的访问控制属性。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
int flags;
if ((flags = fcntl(STDIN_FILENO, F_GETFL)) < 0) {
perror("fcntl get flags");
exit(1);
}
flags |= O_NONBLOCK;
if ((flags = fcntl(STDIN_FILENO, F_SETFL, flags)) < 0) {
perror("fcntl set flags");
exit(1);
}
char buf[10];
ssize_t n;
while (1) {
n = read(0, buf, 5);
if (n >= 0) break;
if (errno != EAGAIN) {
perror("read");
exit(1);
}
write(1, "try again?\n", 11);
sleep(1);
}
write(1, buf, n);
return 0;
}
8.ioctl
用于向设备发控制和配置命令
d:某个设备的文件描述符。
request:ioctl命令,可变参数取决于request,通常是一个指向变量或指针体的指针。
若出错返回-1,若成功返回其他值,返回值也是取决于request。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(void) {
struct winsize size;
if (!isatty(1)) {
printf("1 is not tty\n");
exit(1);
}
if (ioctl(1, TIOCGWINSZ, &size) < 0) {
perror("ioctl");
exit(1);
}
printf("%d rows, %d columns\n", size.ws_row, size.ws_col);
return 0;
}