linux C庫IO和系統IO

一、C庫IO函數工作流程示意圖:

在這裏插入圖片描述
FILE 類型的指針,是特殊結構體類型,包含文件描述符、讀寫指針位置、內存地址等信息,用於文件讀寫操作。

I/O緩衝區用於利用內存減少硬盤操作。在右側三種情況下刷新緩衝區,存到硬盤上。

二、進程控制塊PCB和文件描述符

在這裏插入圖片描述
文件描述符是int類型的。而且每個PCB的文件描述符表中的前三個都是固定的:
標準輸入 fd爲0
標準輸出 fd爲1
標準錯誤 fd爲2
實際上,文件描述表是是個結構體指針數組:
在這裏插入圖片描述

三、虛擬地址空間

程序啓動後,在磁盤上分配4G空間供進程使用,最多4G,用多少分多少。
在這裏插入圖片描述
0-3G在用戶區,程序員可操作;3-4G爲內核區,程序員不可操作。受保護的地址(0-4K)也不許用戶訪問,如NULL在此區域。程序從main函數開始執行,即從代碼段執行,然後根據代碼中變量類型等將元素分配到各個空間中。

四、庫函數與系統函數的關係

在這裏插入圖片描述
由此可見,由於標準C庫函數內部有一個緩衝區,所以可以等緩衝區滿了以後再調用系統IO函數,所以效率提高了。

五、Linux系統IO函數

1、open函數

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

參數:
flags設置:
O_RDONLY 以只讀方式打開文件
O_WRONLY 以只寫的方式打開文件
O_RDWR 以讀寫的方式打開
O_CREAT 如果文件不存在則創建文件
O_EXCL 如果文件存在,則強制 open() 操作失敗
O_TRUNC 如果文件存在,將文件清零
O_APPEND 把文件添加內容的指針設到文件的結束處
mode 設置:
文件權限 = 給定對的文件權限 & 本地掩碼(取反)
例如:
設定權限 0777
如果umask出來的本地掩碼是 0002
777 ----------------------------二進制 111 111 111
002 ----------------------------二進制 00 000 010 取反後得 111 111 101
111 111 111&111 111 101
實際權限 111 111 101
即實際權限爲 0775
返回值:
若成功返回文件描述符(fd);若出錯,返回-1

2、read函數

函數原型:

#include <uinstd.h>
ssize_t read(int fd, void *buf, size_t nbytes);

(讀常規文件是不會阻塞的,不管讀多少字節,read一定會在有限的時間內返回。從終端設備或網絡讀則不一定,如果從終端輸入的數據沒有換行符,調用read讀終端設備就會阻塞,如果網絡上沒有接收到數據包,調用read從網絡讀就會阻塞,至於會阻塞多長時間也是不確定的,如果一直沒有數據到達就一直阻塞在那裏。同樣,寫常規文件是不會阻塞的,而向終端設備或網絡寫則不一定。)
返回值:
-1:error會被置爲相應的值。
error:爲EAGAIN,表示在非阻塞下,此時無數據到達,立即返回。
error:爲EINTR,表示被信號中斷了。

0:對端已關閉,本端也需要close 該套接字。

>0:實際讀取的數據長度。

3、write函數

函數原型:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

返回值:
若成功,返回已寫的字節數;若出錯 返回 -1 ;
open,read,write 函數的運用:從一個文件彙總讀取內容後,寫入另一個文件中,自己動手寫了個例子如下:
先寫一個read_write.c
在這裏插入圖片描述
在這裏插入圖片描述

4、lseek函數

函數原型:
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
作用: 設置文件偏移量。
若文件的偏移量大於當前文件的長度,在這種情況下,對該文件的下一次寫將加長該文件,並在文件中構成一個空洞。位於文件中沒有寫過的字節都被讀爲0.
文件中的空洞並不要求在磁盤上佔用存儲區。
參數:
whence的取值:
SEEK_SET 文件的偏移位置設置爲距開始位置 offset 個字節
SEEK_CUR 文件的偏移位置設置爲當前值 + offset ,offset 的值可正可負
SEEK_END 文件的偏移位置設置爲文件長度 +offset ,offset 的值只能W爲正的,只能向後拓展,不能向前拓展

返回值:
若成功返回從文件頭部開始的偏移量,以字節爲單位(即文件大小);若出錯,返回-1
在這裏插入圖片描述

#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
 
 
int main()
{
    //打開一個已有的文件
	int fd = open("./bb.txt",O_RDWR);
 
	if( fd == -1)
	{
		perror("open bb.txt:");
		exit(1);
	}
 
	int ret = lseek(fd,0,SEEK_END);
	printf("file length = %d\n",ret);
	
	// 文件擴展
	ret = lseek(fd,2000,SEEK_END);
	printf("return value = %d\n",ret);
 
	// 實現文件擴展,需要最後一次寫操作
 
	write(fd,"a",1);
 
	close(fd);
	
	return 0;
}

5.獲取文件屬性—stat、lstat、fstat

函數原型
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf); //struct stat *buf 是函數外創建的,然後扔到函數內去賦值
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
參數
1.path :文件名或者目錄名
2.fd : 文件描述符
3.

struct stat {
    dev_t         st_dev;       //文件的設備編號
    ino_t         st_ino;       //節點
    mode_t        st_mode;      //文件的類型和存取的權限
    nlink_t       st_nlink;     //連到該文件的硬連接數目,剛建立的文件值爲1
    uid_t         st_uid;       //用戶ID
    gid_t         st_gid;       //組ID
    dev_t         st_rdev;      //(設備類型)若此文件爲設備文件,則爲其設備編號
    off_t         st_size;      //文件字節數(文件大小)
    blksize_t     st_blksize;   //塊大小(文件系統的I/O 緩衝區大小)
    blkcnt_t      st_blocks;    //塊數
    time_t        st_atime;     //最後一次訪問時間
    time_t        st_mtime;     //最後一次修改時間
    time_t        st_ctime;     //最後一次改變時間(指屬性)
};

st_mode:下圖中的是8進制下的數據
在這裏插入圖片描述
返回值:
若成功獲取文件屬性,返回0;若失敗,返回 -1;
例子:
使用 stat() 函數實現一個簡單的 ls -l 命令:

vi ls-l.c
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<pwd.h>     // 所有者信息
#include<grp.h>     // 所屬組信息
#include<time.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
 
int main(int argc,char *argv[])
{
	if( argc<2 )
	{
		perror("參數不夠");	
		exit(1);
	}
 
	struct stat st;	//創建一個stat結構體的對象,供系統函數stat使用	
	int i;
	for( i = 1; i<argc; i++)
	{
 
		int ret = stat(argv[i],&st);   // 獲取文件或者目錄的所有信息存儲於 st 結構體中
 
		if( ret == -1 )
		{
			perror("stat");
			exit(1);
		}
 
		// 存儲文件類型和訪問權限
 
		char perms[11] = {0};
 
		// 判斷文件類型
 
		switch( st.st_mode & S_IFMT )
		{
			case S_IFSOCK:   // 套接字文件
				perms[0] = 's';
				break;
			case S_IFLNK:	 // 軟連接文件
				perms[0] = 'l';
				break;
			case S_IFREG:	 // 普通文件
				perms[0] = '-';
				break;
			case S_IFBLK:    // 塊設備文件
				perms[0] = 'b';
				break;
			case S_IFDIR:    // 目錄文件
 
				perms[0] = 'd';
				break;
			case S_IFCHR:    // 字符設備文件
 
				perms[0] = 'c';
				break;
			case S_IFIFO:    // 管道文件
 
				perms[0] = 'p';
				break;
			default:
				break;
 
		}
 
		// 判斷文件的訪問權限
		// 文件的所有者
		perms[1] = (st.st_mode & S_IRUSR) ? 'r':'-';
		perms[2] = (st.st_mode & S_IWUSR) ? 'w':'-';
		perms[3] = (st.st_mode & S_IXUSR) ? 'x':'-';
 
		// 文件的所屬組
		perms[4] = (st.st_mode & S_IRGRP) ? 'r':'-';
		perms[5] = (st.st_mode & S_IWGRP) ? 'w':'-';
		perms[6] = (st.st_mode & S_IXGRP) ? 'x':'-';
 
		// 文件的其他用戶
 
		perms[7] = (st.st_mode & S_IROTH) ? 'r':'-';
		perms[8] = (st.st_mode & S_IWOTH) ? 'w':'-';
		perms[9] = (st.st_mode & S_IXOTH) ? 'x':'-';
 
		// 硬鏈接計數
 
		int nums = st.st_nlink;
 
		// 文件所有者
 
		char *fileuser = getpwuid(st.st_uid)->pw_name;
 
		// 文件所屬組
		char *filegroup = getgrgid(st.st_gid)->gr_name;
 
		// 文件大小
		int size = (int)st.st_size;
 
		// 文件修改時間
 
		char *time = ctime(&st.st_mtime);
		char mtime[512]="";
		strncpy(mtime,time,strlen(time)-1);
 
		// 保存輸出信息格式
		char buf[1024]={0};
 
		// 把對應信息按格式輸出到 buf 中
		sprintf(buf,"%s %d %s %s      %d %s %s",perms,nums,fileuser,filegroup,size,mtime,argv[i]);
 
		// 打印 buf 
		printf("%s\n",buf);
 
	}
 
	return 0;
}

stat、lstat、fstat之間的區別:
1.fstat 函數:形參是”文件描述符”,而另外兩個形參是“文件路徑”。文件描述符是我們用 open 系統調用後得到的,而文件路徑直接寫就可以了。
2.stat 函數與 lstat 函數的區別: 當一個文件是軟鏈接時,lstat 函數返回的是該軟鏈接本身的信息 (不穿透);而 stat 函數返回的是該軟鏈接指向文件的信息 (穿透)

stat與 lstat 對比的例子:

vi ls-l.c

在這裏插入圖片描述

gcc ./ls-l.c -o ls-l
ln -s main.c main.soft

接下來我們要使用剛生成的可執行文件來觀察main.c和其軟鏈接main.soft的大小。

ls -l

在這裏插入圖片描述

./ls-l main.c main.soft

在這裏插入圖片描述
驗證了確實stat具有穿透性,stat 函數返回的是該軟鏈接指向文件的信息。
接下來我們把ls-l.c中的stat改成lstat,重新編譯後重復執行上面步驟後再來觀察:
在這裏插入圖片描述
驗證了lstat不具有穿透性。

6.unlink

在這裏插入圖片描述
例:使用unlink的特性創建臨時文件。

vi unlink.c

在這裏插入圖片描述

gcc unlink.c -o unlink
./unlink

在這裏插入圖片描述

7.其他常見的文件操作的系統IO

在這裏插入圖片描述
在這裏插入圖片描述
接下來看目錄操作的系統IO

8.chdir

man 2 chdir

1.作用:修改當前進程的路徑
2.函數原型:
#include <unistd.h>
int chdir(const char *path);

9.getcwd

1.作用:獲取當前進程的工作目錄
2.函數原型:
#include <unistd.h>
char *getcwd(char *buf, size_t size);
char *getwd(char *buf);
例子:chdir 函數和 getcwd 函數的運用:

#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
 
 
int main(int argc, char *argv[] )
{
 
	if( argc<2 )
	{
		perror("參數不夠");
		exit(1);
	}
 
	printf(" agrv[1] = %s\n",argv[1]);
	// 修改當前的路徑
	int ret =chdir(argv[1]);
	if( ret == -1 )
	{
		perror("chdir");
		exit(1);	
	}
 
	// 在這裏通過在改變後的目錄下創建一個新的文件,來證明目錄已經改變
	int fd = open("chdir.txt",O_CREAT|O_RDWR,0644);
	if( fd == -1 )
	{
		perror("open");	
		exit(1);
	}
 
	close(fd);
 
	// 獲取改變目錄後的目錄名
	char buf[100]={0};
 
	getcwd(buf,sizeof(buf));
	printf("current dir: %s\n",buf);
 
	return 0;
}

10.rmdir

1.作用:刪除一個目錄
2.函數原型:
#include <unistd.h>
int rmdir(const char *pathname);

11.mkdir

1.作用:創建一個目錄
2.函數原型:
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
mode就是權限
在這裏插入圖片描述

12.opendir

1.作用:打開一個目錄
2.函數原型
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
在這裏插入圖片描述

13.readdir

1.作用:讀目錄
2.函數原型:
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
3.返回值:返回一個記錄項(即一個結構體對象)
在這裏插入圖片描述

14.closedir

1.作用:關閉一個目錄
2.函數原型:
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
3.返回值:
若函數執行成功,返回0;若失敗,返回 -1.
例子:遞歸讀目錄獲取普通文件的個數

vi file_count.c
#include<unistd.h>
#include<dirent.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
 
 
// 獲取 root 目錄下的文件個數
int get_file_count(char *root)
{
	// open dir
	DIR * dir = NULL;
	dir = opendir(root);
	if( NULL == dir )
	{
		perror("opendir");
		exit(1);
	}
 
	// 遍歷當前打開的目錄
	struct dirent* ptr = NULL;
	char path[1024]={0};
	int total = 0;
	while( (ptr = readdir(dir) )!= NULL)
	{
		// 過濾掉 . 和 ..
		if( strcmp(ptr->d_name,".") == 0 || strcmp(ptr->d_name,"..") == 0 )
		{		
			continue;	
		}
	
		// 如果是目錄,遞歸讀目錄
		if(ptr->d_type == DT_DIR)
		{
			sprintf(path,"%s/%s",root,ptr->d_name);
			total += get_file_count(path);
		}
 
		// 如果是普通文件
		if( ptr->d_type == DT_REG )
		{
				total++;	
		}
	}
	
	// 關閉目錄
	closedir(dir);
	return total;
}
 
int main(int argc,char *argv[])
{
	if( argc<2 )
	{
		perror("參數不夠");
		exit(1);	
	}
 
	// 獲取指定目錄下普通文件的個數
	int count =	get_file_count(argv[1]);
 
	printf("%s has file numbers : %d\n",argv[1],count);	
	return 0;
}

在這裏插入圖片描述

15.dup和dup2(重定向文件描述符)

1.作用:複製現有的文件描述符
2.函數原型:
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);

3.返回值:
(1).dup返回的是文件描述符中沒有被佔用的,然後就可以做到dup返回的文件描述符和oldfd都指向同一個文件。
(2).dup2 分兩種情況討論下:
(a).如果oldfd和newfd不相同,那麼在拷貝前會先關掉newfd對應的文件,然後newfd被重定向,這樣oldfd和newfd就都指向同一個文件了。
(b).如果oldfd和newfd是同一個文件描述符,不會關掉newfd , 直接返回oldfd,,這樣顯然oldfd和newfd也是指向同一個文件。

dup的例子如下:

vi dup.c
vi a.txt

先在a.txt裏隨便寫一句話:
在這裏插入圖片描述

#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
 
int main(void)
{
	int fd =open("a.txt",O_RDWR);//打開一個已有的文件
	if( fd == -1 )
	{
		perror("open");
		exit(1);
	}
 
	printf("file open fd = %d\n",fd);
 
	// 找到進程文件描述符表中第一個可用的文件描述符
	// 將參數指定的文件複製到該描述後,返回這個描述符
 
	int ret = dup(fd);
	if( fd == -1 )
	{
		perror("dup");
		exit(1);
	}
 
	printf(" dup fd = %d\n",ret);
	char *buf = "你是猴子請來的救兵嗎??\n";
	char *buf1 = "你大爺的,我是程序猿!!!\n";
    lseek(fd,0,SEEK_END);//注意,如果不想原有內容被覆蓋,就要移動文件指針到末尾
	write(fd,buf,strlen(buf));
	write(ret,buf1,strlen(buf1));
 
	close(fd);
	
	return 0;
}
gcc ./dup.c -o ./dup
./dup

在這裏插入圖片描述

vi a.txt

在這裏插入圖片描述
dup2的例子如下:

#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
 
int main(void)
{
	int fd =open("english.txt",O_RDWR);
	if( fd == -1 )
	{
		perror("open");
		exit(1);
	}
 
	int fd1 =open("a.txt",O_RDWR);
	if( fd1 == -1 )	
	{
		perror("open");
		exit(1);
	}
 
	printf("fd = %d\n",fd);
	printf("fd1 = %d\n",fd1);
 
	int ret = dup2(fd1, fd);//關閉fd也就是english.txt,然後fd被重定向,即fd也指向了a.txt
	if( ret == -1 )
	{	
		perror("dup2");
		exit(1);
	}
 
	printf(" current fd = %d\n",ret);
 
	char *buf = "主要看氣質\n";
    lseek(fd,0,SEEK_END);//注意,如果不想原有內容被覆蓋,就要移動文件指針到末尾
	write(fd,buf,strlen(buf));
	write(fd1,"hello world!",12);
    //以上兩句都是對a.txt進行操作
	close(fd);
	close(fd1);
 
	return 0;
}

16.fnctl(網絡編程會用到)

在這裏插入圖片描述
fnctl的例子如下:

#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
 
int main(void)
{
	int flag;
	int fd;
 
	// 測試字符串
 
	char *p = "我們是一個由中國特色社會主義的國家";
	char *q ="社會主義好哇";
 
	// 以只寫方式打開文件
 
	fd = open("test.txt",O_WRONLY);
	if( fd == -1 )
	{
		perror("open");
		exit(1);
	
	}
 
	// 輸入新的內容,該內容會覆蓋原來的內容
	if( write(fd,p,strlen(p)) == -1 )
	{
		perror("write");
		exit(1);
	}
 
	//使用 F_GETFL 命令得到文件狀態標誌
	flag = fcntl(fd,F_GETFL,0); //第二個參數爲F_GETFL時,第三個參數固定是0
	if( flag == -1 )
	{
		perror("fcntl");
		exit(1);
	}
 
	//	將文件狀態標誌添加 “追加寫” 選項
 
	flag |= O_APPEND;
 
	// 將文件狀態修改爲追加寫(注意,修改後文件指針會自動移到尾部!!!)
 
	if( fcntl(fd,F_SETFL,flag) == -1 )
	{
		perror("fcntl");
		exit(1);
	}
 
	// 再次輸入新的內容,該內容會追加到最後
 
	if( write(fd,q,strlen(q)) == -1 )
	{
		perror("write again");
		exit(1);
	}
	
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章