1. 程式人生 > >Linux文件的I/O操作

Linux文件的I/O操作

sign 基本 div 設備文件 獲取文件 cor man 交互 等待

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.

設置偏移量寫


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);

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

總結

技術分享圖片

技術分享圖片技術分享圖片

Linux文件的I/O操作