Linux文件的I/O操作
C标准函数与系统函数的区别
标准函数printf调用应用层api,然后应用层api调用内核层api,再通过内核层api调用硬件设备
一个pirntf打印helloworld那么sys_write需要输出几次到显示设备?
Printf把helloworld送到缓冲区,然后由"文件表述符一次执行一个字符"一共10次
然后送到缓冲区,再有sys_write一次输出到显示设备
I/O缓冲区
每一个FILE文件流都有一个缓冲区buffer,默认大小8192Byte。
文件描述符
一个进程默认打开3个文件描述符
STDIN_FILENO 0
STDOUT_FILENO 1
STDERR_FILENO 2
Open/close
新打开文件返回文件描述符表中未使用的最小文件描述符。
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);
返回值:成功返回新分配的文件描述符,出错返回-1并设置errno
int open(const char *pathname, int flags, ...);
一部分man 2 open 参考文档
open(2)的Man Page:
* O_APPEND 表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾
而不覆盖原来的内容。
* O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode,表示该
文件的访问权限。
* O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
* O_TRUNC 如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断(Truncate)
为0字节。
* O_NONBLOCK 对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O(Nonblock I/
O),非阻塞I/O在下一节详细讲解。
注意open函数与C标准I/O库的fopen函数有些细微的区别:
以可写的方式fopen一个文件时,如果文件不存在会自动创建,而open一个文件时必须
明确指定O_CREAT才会创建文件,否则文件不存在就出错返回。
以w或w+方式fopen一个文件时,如果文件已存在就截断为0字节,而open一个文件时必
须明确指定O_TRUNC才会截断文件,否则直接在原来的数据上改写。
第三个参数mode指定文件权限,可以用八进制数表示,比如0644表示-rw-r-r–,也可
以用S_IRUSR、S_IWUSR等宏定义按位或起来表示,详见open(2)的Man Page。要注意的是,
文件权限由open的mode参数和当前进程的umask掩码共同决定。
O_CREAT
使用o_CREAT创建一个文件
python@ubuntu:~/linuxC$ cat open.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
int fd;
fd=open("abc",O_CREAT,0777); //创建文件后的权限
printf("fd=%d",fd);
return 0;
}
python@ubuntu:~/linuxC$ gcc open.c -o app
python@ubuntu:~/linuxC$ ./app
python@ubuntu:~/linuxC$ ls -l
总用量 16
-rwxrwxr-x 1 python python 0 1月 19 18:39 abc
-rwxrwxr-x 1 python python 8656 1月 19 18:40 app
-rw-rw-r-- 1 python python 197 1月 19 18:39 open.c
使用argc
python@ubuntu:~/linuxC$ cat createFile.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
//argc传递执行命令的个数 argv记录命令
int main(int argc,char *argv[])
{
int fd;
printf("%d",argc);
if (argc<2){
printf("./app fileName\n");
exit(1);//在任何函数中调用程序结束或程序退出
}
fd=open(argv[1],O_CREAT,0644);
printf("fd=%d\n",fd);
return 0;//调用是函数返回,在main是程序结束
}
python@ubuntu:~/linuxC$ gcc createFile.c -o app
python@ubuntu:~/linuxC$ ./app linux
2fd=3
python@ubuntu:~/linuxC$ ls -l
总用量 20
-rw-r--r-- 1 python python 0 1月 19 18:50 2
-rwxrwxr-x 1 python python 0 1月 19 18:39 abc
-rwxrwxr-x 1 python python 8760 1月 19 18:53 app
-rw-rw-r-- 1 python python 445 1月 19 18:50 createFile.c
-rw-r--r-- 1 python python 0 1月 19 18:53 linux
-rw-rw-r-- 1 python python 197 1月 19 18:39 open.c
-rw-r--r-- 1 python python 0 1月 19 18:53 wer
O_RDWR
读写文件
python@ubuntu:~/linuxC$ cat writeFile.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd;
char buf[1024]="helloworld";
if (argc < 2){
printf("./app fileName\n");
exit(1);
}
fd = open(argv[1],O_CREAT | O_RDWR,0644);
write(fd, buf, strlen(buf));
printf("fd=%d\n",fd);
close(fd);
return 0;
}
python@ubuntu:~/linuxC$ gcc writeFile.c -o app
python@ubuntu:~/linuxC$ ./app linux
fd=3
python@ubuntu:~/linuxC$ cat linux
helloworldpython@ubuntu:~/linuxC$
O_EXCL
并且文件已存在,则出错返回
python@ubuntu:~/linuxC$ cat writeFile.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd;
char buf[1024]="helloworld";
if (argc < 2){
printf("./app fileName\n");
exit(1);
}
fd = open(argv[1],O_CREAT | O_RDWR | O_EXCL ,0644);
write(fd, buf, strlen(buf));
printf("fd=%d\n",fd);
close(fd);
return 0;
}
python@ubuntu:~/linuxC$ gcc writeFile.c -o app
//因为文件中有了,所有在创建的是哈报错了
python@ubuntu:~/linuxC$ ./app linux
fd=-1
python@ubuntu:~/linuxC$ ./app test
fd=3
python@ubuntu:~/linuxC$ ls -l
总用量 32
-rw-r--r-- 1 python python 0 1月 19 18:50 2
-rwxrwxr-x 1 python python 0 1月 19 18:39 abc
-rwxrwxr-x 1 python python 8976 1月 19 19:10 app
-rw-rw-r-- 1 python python 445 1月 19 18:50 createFile.c
-rw-r--r-- 1 python python 10 1月 19 19:01 linux
-rw-rw-r-- 1 python python 197 1月 19 18:39 open.c
-rw-r--r-- 1 python python 10 1月 19 19:10 test
-rw-r--r-- 1 python python 0 1月 19 18:53 wer
-rw-rw-r-- 1 python python 450 1月 19 19:10 writeFile.c
O_APPEND
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd;
char buf[1024]="helloworld";
if (argc < 2){
printf("./app fileName\n");
exit(1);
}
fd = open(argv[1], O_RDWR | O_APPEND ,0644);
write(fd, buf, strlen(buf));
printf("fd=%d\n",fd);
close(fd);
return 0;
}
python@ubuntu:~/linuxC$ gcc writeFile.c -o app
python@ubuntu:~/linuxC$ ./app linux //追家内容
fd=3
python@ubuntu:~/linuxC$ cat linux
helloworldhelloworldpython@ubuntu:~/linuxC$ ./app linux
fd=3
python@ubuntu:~/linuxC$ cat linux
helloworldhelloworldhelloworldpython@ubuntu:~/linuxC$
umaks权限
python@ubuntu:~/linuxC$ cat writeFile.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
umask(0);//默认设置为0 在当前进程
int main(int argc,char *argv[])
{
int fd;
char buf[1024]="helloworld";
if (argc < 2){
printf("./app fileName\n");
exit(1);
}
fd = open(argv[1], O_RDWR | O_APPEND ,0777); //这样就不会执行当前环境先的umask所以也就能设置这个文件权限777了
write(fd, buf, strlen(buf));
printf("fd=%d\n",fd);
close(fd);
return 0;
}
实验
输入一个文件拷贝内容到另外的一个文件中
python@ubuntu:~/linuxC$ cat copyFile.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define SIZE 8192
int main(int argc,char *argv[])
{
char buf[SIZE];
int fd_src,fd_dest,len;
if(argc<3)
{
printf("./mycp src dest\n");
exit(1);
}
fd_src = open(argv[1],O_RDONLY);
fd_dest = open (argv[2],O_CREAT | O_WRONLY | O_TRUNC , 0633);
/*
* 成功返回读取字节数
* 督导文件末尾返回0
* 读取失败返回-1
*/
while((len=read(fd_src,buf,sizeof(buf)))>0)
{
write(fd_dest,buf,len);
}
close(fd_src);
close(fd_dest);
return 0;
}
python@ubuntu:~/linuxC$ gcc copyFile.c -o app
python@ubuntu:~/linuxC$ ./app linux test
python@ubuntu:~/linuxC$ cat test
helloworldhelloworldhelloworld
阻塞和非阻塞
读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端
设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻
塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是
不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而
向终端设备或网络写则不一定。
现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被
置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比
如网络上接收到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡
眠状态相对的是运行(Running)状态,在Linux内核中,处于运行状态的进程分为两种情
况:
正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip)里保存着该进程
的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正
在读写该进程的地址空间。
就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另
一个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进
程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程
的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时
要兼顾用户体验,不能让和用户交互的进程响应太慢。
下面这个小程序从终端读数据再写回终端。
阻塞读取终端
python@ubuntu:~/linuxC$ cat terminal.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h> //POSIX标准定义的unix类系统定义符号常量的头文件, 包含了许多UNIX系统服务的函数原型,
int main()
{
char buf[10];
int n;
n=read(STDIN_FILENO,buf,10); //标准等待输入
if (n < 0 )
{
perror("read STDIN_FILENO");
exit(1);
}
write(STDOUT_FILENO,buf,n); //标准输出
return 0;
}
python@ubuntu:~/linuxC$ gcc terminal.c -o app
python@ubuntu:~/linuxC$ ./app
linux
linux
非阻塞读终端
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define MSG_TRY "try again\n"
int main(void)
{
char buf[10];
int fd, n;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if(fd<0) {
perror("open /dev/tty");
exit(1);
}
tryagain:
n = read(fd, buf, 10);
if (n < 0) {
if (errno == EAGAIN) {
sleep(1); //没有文件那么休眠1秒,打印一句话,接着在来读取一次
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
goto tryagain;
}
perror("read /dev/tty");
exit(1);
}write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}
非阻塞读终端和等待超时
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define MSG_TRY "try again\n"
#define MSG_TIMEOUT "timeout\n"
int main(void)
{
char buf[10];
int fd, n, i;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if(fd<0) {
perror("open /dev/tty");
exit(1);
}
for(i=0; i<5; i++) {
n = read(fd, buf, 10);
if(n>=0)
break;
if(errno!=EAGAIN) { //没有文件就是EAGAIN
perror("read /dev/tty");
exit(1);
}
sleep(1);
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
}
if(i==5)
write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
else
write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}
errno标识
The <errno.h> header file defines the integer variable errno, which is set by
system calls and some library functions in the event of an error to indicate
what went wrong. Its value is significant only when the return value of the
call indicated an error (i.e., -1 from most system calls; -1 or NULL from most
library functions); a function that succeeds is allowed to change errno.
Valid error numbers are all nonzero; errno is never set to zero by any system
call or library function.
errno 会从系统定义的错误文件中,匹配到一个值,并且打印对应错误值的消息
python@ubuntu:~/linuxC$ cat errno.c
#include <sys/stat.h>//文件的属性
#include <unistd.h>// POSIX 操作系统 API 的访问功能的头文件的名称
#include <sys/types.h> //基本系统数据类型
#include <fcntl.h>//定义了很多宏和open
#include <stdio.h>
#include <errno.h> //错误的标志
int main()
{
int fd=open("abc",O_WRONLY);
if ( fd < 0)
{
printf("errno=%d\n",errno);
perror("main open"); //错误标志前面的提示
}
printf("fd=%d\n",fd);
return 0;
}
python@ubuntu:~/linuxC$ gcc errno.c -o app
python@ubuntu:~/linuxC$ ./app
errno=2 //当前的标准为2
main open: No such file or directory
fd=-1
系统默认的标志
python@ubuntu:~/linuxC$ cat /usr/include/asm-generic/errno-base.h
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */ //这是刚刚上面的那一条
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#endif
strerron
python@ubuntu:~/linuxC$ cat errno.c
#include <sys/stat.h>//文件的属性
#include <unistd.h>// POSIX 操作系统 API 的访问功能的头文件的名称
#include <sys/types.h> //基本系统数据类型
#include <fcntl.h>//定义了很多宏和open
#include <stdio.h>
#include <errno.h> //错误的标志
#include <string.h>
int main()
{
int fd=open("abc",O_WRONLY);
if ( fd < 0)
{
printf("errno=%d\n",errno);
// perror("main open");
printf("main open %s\n",strerror(errno));
}
printf("fd=%d\n",fd);
return 0;
}
python@ubuntu:~/linuxC$ gcc errno.c -o app
python@ubuntu:~/linuxC$ ./app
errno=2
main open No such file or directory //跟error差不多只是,没有:而已
fd=-1
lseek
每个打开的文件都记录着当前读写位置,打开文件时读写位置是0,表示文件开头,通
常读写多少个字节就会将读写位置往后移多少个字节。但是有一个例外,如果以O_APPEND方
式打开,每次写操作都会在文件末尾追加数据,然后将读写位置移到新的文件末尾。lseek
和标准I/O库的fseek函数类似,可以移动当前读写位置(或者叫偏移量)。
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
DESCRIPTION
The lseek() function repositions the offset of the open file associated with
the file descriptor fd to the argument offset according to the directive whence
as follows:
SEEK_SET
The offset is set to offset bytes.
SEEK_CUR
The offset is set to its current location plus offset bytes.
SEEK_END
The offset is set to the size of the file plus offset bytes.
设置偏移量写
#include <sys/stat.h>//文件的属性
#include <unistd.h>// POSIX 操作系统 API 的访问功能的头文件的名称
#include <sys/types.h> //基本系统数据类型
#include <fcntl.h>//定义了很多宏和open
#include <stdlib.h>
#include <errno.h> //错误的标志
#include <string.h>
#include <stdio.h>
int main()
{
int fd = open("abc",O_RDWR);
if ( fd <0 )
{
perror("open abc");
return 0;
}
lseek(fd, 0x1000, SEEK_SET); //0x1000 4k
write(fd, "a",1);
close(fd);
return 0;
}
python@ubuntu:~/linuxC$ gcc lseek.c -o app
python@ubuntu:~/linuxC$ touch abc
python@ubuntu:~/linuxC$ ls -l
总用量 44
-rw-r--r-- 1 python python 0 1月 19 18:50 2
-rw-rw-r-- 1 python python 0 1月 22 11:09 abc
python@ubuntu:~/linuxC$ ./app
python@ubuntu:~/linuxC$ ls -l
总用量 48
-rw-r--r-- 1 python python 0 1月 19 18:50 2
-rw-rw-r-- 1 python python 4097 1月 22 11:09 abc
-rw-rw-r-- 1 python python 454 1月 19 19:17 writeFile.c
获取文件的大小
python@ubuntu:~/linuxC$ cat lseek.c
#include <sys/stat.h>//文件的属性
#include <unistd.h>// POSIX 操作系统 API 的访问功能的头文件的名称
#include <sys/types.h> //基本系统数据类型
#include <fcntl.h>//定义了很多宏和open
#include <stdlib.h>
#include <errno.h> //错误的标志
#include <string.h>
#include <stdio.h>
int main()
{
int fd = open("abc",O_RDWR);
if ( fd <0 )
{
perror("open abc");
return 0;
}
lseek(fd, 0x1000, SEEK_SET); //0x1000 4k
write(fd, "a",1);
printf("abc Size=%d\n",lseek(fd,0,SEEK_END));
close(fd);
return 0;
}
python@ubuntu:~/linuxC$ ./app
abc Size=4097
Fcntl
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define MSG_TRY "try again\n"
int main(void)
{
char buf[10];
int n;
int flags;
flags = fcntl(STDIN_FILENO, F_GETFL); //获取
flags |= O_NONBLOCK; //设置为非阻塞
if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) { //在设置回去为非阻塞
perror("fcntl");
exit(1);
}
tryagain:
n = read(STDIN_FILENO, buf, 10);
if (n < 0) {
if (errno == EAGAIN) {
sleep(1);
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
goto tryagain;
}
perror("read stdin");
exit(1);
}
write(STDOUT_FILENO, buf, n);
return 0;
}
ioctl
ioctl用于向设备发控制和配置命令,有些命令也需要读写一些数据,但这些数据是
不能用read/write读写的,称为Out-of-band数据。也就是说,read/write读写的数据是
in-band数据,是I/O操作的主体,而ioctl命令传送的是控制信息,其中的数据是辅助的数
据。例如,在串口线上收发数据通过read/write操作,而串口的波特率、校验位、停止位通
过ioctl设置,A/D转换的结果通过read读取,而A/D转换的精度和工作频率通过ioctl设置。
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
d是某个设备的文件描述符。request是ioctl的命令,可变参数取决于request,通常是
一个指向变量或结构体的指针。若出错则返回-1,若成功则返回其他值,返回值也是取决于
request。
以下程序使用TIOCGWINSZ命令获得终端设备的窗口大小。
python@ubuntu:~/linuxC$ cat ioct.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(void)
{
struct winsize size;
if (isatty(STDOUT_FILENO) == 0) //终端的输出是否0当前
exit(1);
if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)<0) { //把当前终端的行和列复制到size
perror("ioctl TIOCGWINSZ error");
exit(1);
}
printf("%d rows, %d columns\n", size.ws_row, size.ws_col);
return 0;
}
python@ubuntu:~/linuxC$ gcc ioct.c -o app
python@ubuntu:~/linuxC$ ./app
24 rows, 89 columns
总结