1. 程式人生 > >linux系統程式設計(二)--檔案操作

linux系統程式設計(二)--檔案操作

1.0    檔案描述符

        每個程序啟動後會自動開啟三個檔案描述符 0、1、2   

         分別對應於巨集 STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO    

2.0   標準IO的緩衝型別

       緩衝型別可以分為:無緩衝,行緩衝,與全緩衝

        標準輸出是"行緩衝",即遇到換行符或程序結束才會真正執行 IO 操作

         標準出錯中"非緩衝"的,即立即進行 IO 操作

         如果是磁碟檔案,標準 IO 會在其緩衝區填滿後才真正進行 IO 操作

//獲取緩衝區的大小
void get_buf(FILE *fd)
{
    printf("buf star:%p\n",fd->_IO_buf_base);
    printf("buf end:%p\n",fd->_IO_buf_end);
    printf("%d\n",(int )(fd->_IO_buf_end - fd->_IO_buf_base));
}

	//讓緩衝區在buf上分配
	 if(setvbuf(fp,buf,_IOFBF,1314) == 0)
	{
		printf("setvbuf OK\n");
	}	

3.0 開啟和關閉檔案

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

開啟檔案,返回檔案描述符,失敗返回 -1:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

flags:
1. 必須包含以下三個之一:
O_RDONLY, O_WRONLY, O_RDWR

2. 還可以包含以下 0 到 n 個建立標誌(按位或):
O_CLOEXEC,O_CREAT,O_DIRECTORY,O_EXCL,O_NOCTTY,O_NOFOLLOW,O_TMPFILE

3. 以及其它檔案狀態標誌(按位或)

O_CREAT|O_EXCL 如果檔案已存在,則返回失敗
O_TRUNC 截短檔案長度為 0




建立檔案,返回檔案描述符,失敗返回 -1:
int creat(const char *pathname, mode_t mode);

等價於 open() 函式的 O_CREAT|O_WRONLY|O_TRUNC 標誌

以上兩個函式的 mode 可能使用巨集或 5 位八進位制數來表示許可權


#include <unistd.h>

關閉檔案
int close(int fd);

  4.0 檔案讀寫

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

讀從檔案描述符 fd 代表的檔案讀取最多 count 個位元組資料到 buf 中
如果 count 超了 SSIZE_MAX,則結果未定義

返回值 <= count,-1 表示出錯,0 表示檔案尾,其它的表示成功
成功並不代表讀取到了 count 個位元組

ssize_t write(int fd, const void *buf, size_t count);

將 buf 中的最多 count 個位元組資料寫入 fd 檔案中
返回值 <= count,-1 表示出錯,0 表示什麼也沒有寫,其它表示成功
成功並不代表寫入了 count 個位元組

	// 複製原始檔到目的檔案
	while (1)
	{
		cnt = read(srcfd, buf, sizeof buf);
		if (-1 == cnt)
		{
			perror("讀檔案失敗");
			break;
		}
		else if (0 == cnt)
		{
			printf("複製完畢\n");
			break;
		}
		  
		// 要寫入的 cnt 個位元組不一定能一次寫成功
		// 可以加入程式碼實現必須寫完 cnt 個位元組才進行下一輪複製
     
		/*if (-1 == write(destfd, buf, cnt))
		{	
			perror("寫檔案失敗");
			break;
		}*/

        char *p = buf;
        int num ;

        while(1)
        {
            num = write(destfd, p , cnt);
        
            if(-1 == num)
            {
			    perror("寫檔案失敗");
			    break;
            }
            else if(num < cnt)
            {
                cnt = cnt - num;
                p = p + num;
            }
            else
            {
                 break;
            }
         }

	}


5.0 檔案讀寫效率

       緩衝區越大,效率越高。

6.0   lseek 

#include <sys/types.h>
#include <unistd.h>

移動 fd 檔案的位置指標到基於 whence 偏移 offset 個位元組
off_t lseek(int fd, off_t offset, int whence);

whence 取值:
SEEK_SET  檔案頭
SEEK_CUR  當前位置
SEEK_END  檔案尾

7.0  link硬連結

     

#include <unistd.h>

為已存在的 oldpath 檔案建立硬連結 newpath
如果 newpath 已存在,則不會被覆蓋
成功返回 0,失敗返回 -1
int link(const char *oldpath, const char *newpath);

8.0  symlink符號連結

#include <unistd.h>

為已存在的檔案/目錄 target 建立符號連結 linkpath
符號連結可以指向已存在的檔案,也可以指向不存在的檔案
如果 linkpath 已存在則不會被覆蓋
成功返回 0,失敗返回 -1
int symlink(const char *target, const char *linkpath);

9.0 unlink刪除檔案

#include <unistd.h>

刪除 pathname 檔案,並減少其硬連結數目,不能刪除目錄
如果是最後一個硬連結,則會從檔案系統刪除此檔案
此時如果有任何程序要開啟此檔案,則直到程序關閉了開啟的檔案
才會真正刪除它
如果刪除的是符號連結檔案,則刪除符號連結檔案本身
如果刪除套接字、FIFO 或裝置,則名字會被刪除,但擁有它的程序
可以繼續使用它
成功返回 0,失敗返回 -1
int unlink(const char *pathname);

10  remove刪除檔案或目錄

#include <stdio.h>

刪除 pathname 檔案或目錄
它呼叫 unlink 刪除檔案,呼叫 rmdir 刪除目錄
刪除目錄時,目錄必須為空
成功返回 0,失敗返回 -1
int remove(const char *pathname);

11 mkdir建立目錄

#include <sys/stat.h>
#include <sys/types.h>

建立 pathname 目錄,設定模式為 mode
模式=(mode & ~umask & 0777)
成功返回 0,失敗返回 -1

int mkdir(const char *pathname, mode_t mode);

int main(int argc, char **argv)
{
	char *pathname;

	if (2 != argc)
	{
		printf("用法:%s <目錄名>\n", argv[0]);
		return -1;
	}
	pathname = argv[1];

	// 建立目錄
	if (-1 == mkdir(pathname, 0755))
	{
		perror("建立目錄失敗");
		return -1;
	}

	return 0;
}

12  rmdir刪除目錄

#include <unistd.h>

刪除 pathname 目錄,目錄必須為空
成功返回 0,失敗返回 -1
int rmdir(const char *pathname);

13 chdir切換目錄

#include <unistd.h>

切換工作目錄到 path
成功返回 0,失敗返回 -1
int chdir(const char *path);
int fchdir(int fd);

獲取當前工作目錄的絕對路徑,儲存到大小為 szie 的 buf 中
glibc 庫的 getcwd 在 buf 為 NULL 時,會使用 malloc 動態
分配記憶體用於存放路徑,此時呼叫者必須使用 free 來釋放記憶體
char *getcwd(char *buf, size_t size);

獲取工作目錄的絕對路徑,它不會動態分配記憶體來存放路徑,
使用者必須提供不小於 PATH_MAX 大小的記憶體用於存放路徑
char *getwd(char *buf);

獲取工作目錄的絕對路徑,使用 malloc 分配記憶體來存放路徑,
呼叫者必須使用 free 來釋放分配的記憶體
char *get_current_dir_name(void);

以上函式,成功返回指向路徑的字串指標,失敗返回 NULL

注意:
    程序用 chdir 函式切換目錄,只在當前程序下有效,當
	退出程序回到父 shell,不會影響父 shell 的工作目錄


int main(int argc, char **argv)
{
	char *path, pathbuf[256] = "";

	if (2 != argc)
	{
		printf("用法:%s <目錄>\n", argv[0]);
		return -1;
	}
	path = argv[1];

	// 切換目錄
	if (-1 == chdir(path))
	{
		perror("切換目錄失敗");
		return -1;
	}

	// 獲取當前工作目錄
	if (NULL == getcwd(pathbuf, sizeof pathbuf))
	{
		perror("獲取當前工作目錄失敗");
		return -1;
	}

	printf("已切換到目錄:%s\n", pathbuf);

	return 0;
}

14  掃描目錄

 

#include <sys/types.h>
#include <dirent.h>

開啟目錄 name 或 fd
成功返回目錄流指標,指標指向目錄流的第一個入口;
失敗返回 NULL
DIR *opendir(const char *name);
DIR *fdopendir(int fd);

#include <dirent.h>

讀目錄 dirp
成功返回目錄流的下一個入口(即目錄項);
失敗或到達目錄流尾返回 NULL。到達流尾時不會修改 errno 的值
注意,返回值有可能被後續的呼叫所覆蓋,因此此函式是“不可重入的”
struct dirent *readdir(DIR *dirp);
 
以下是“可重入”版本
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);

目錄流入口結構體
struct dirent {
	ino_t          d_ino;       /* inode number */
	off_t          d_off;       /* not an offset; see NOTES */
	unsigned short d_reclen;    /* length of this record */
	unsigned char  d_type;      /* type of file; not supported
								   by all filesystem types */
	char           d_name[256]; /* filename */
};
struct dirent
{
   long d_ino; /* inode number 索引節點號 */
   off_t d_off; /* offset to this dirent 在目錄檔案中的偏移 */
   unsigned short d_reclen; /* length of this d_name 檔名長 */
   unsigned char d_type; /* the type of d_name 檔案型別 */
   char d_name [NAME_MAX+1]; /* file name (null-terminated) 檔名,最長255字元 */
}


#include <sys/types.h>
#include <dirent.h>

關閉目錄流 dirp
成功返回 0,失敗返回 -1
int closedir(DIR *dirp);

15  檔案狀態

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

獲取檔案 pathname/fd 的狀態,儲存到 buf 中
如果 pathname 是連結檔案,則返回的是它所指向的檔案的狀態
int stat(const char *pathname, struct stat *buf);

int fstat(int fd, struct stat *buf);

以下函式類似於 stat 函式,但當 pathname 是一個連結檔案時,
返回的是連結檔案本身的狀態,而不是它所指向的檔案的狀態
int lstat(const char *pathname, struct stat *buf);

以上函式成功返回 0,失敗返回 -1


狀態結構體:
struct stat {
	dev_t     st_dev;         /* ID of device containing file */
	ino_t     st_ino;         /* inode number */
	mode_t    st_mode;        /* protection */
	nlink_t   st_nlink;       /* number of hard links */
	uid_t     st_uid;         /* user ID of owner */
	gid_t     st_gid;         /* group ID of owner */
	dev_t     st_rdev;        /* device ID (if special file) */
	off_t     st_size;        /* total size, in bytes */
	blksize_t st_blksize;     /* blocksize for filesystem I/O */
	blkcnt_t  st_blocks;      /* number of 512B blocks allocated */

	/* Since Linux 2.6, the kernel supports nanosecond
	   precision for the following timestamp fields.
	   For the details before Linux 2.6, see NOTES. */

	struct timespec st_atim;  /* time of last access */
	struct timespec st_mtim;  /* time of last modification */
	struct timespec st_ctim;  /* time of last state.s change */

#define st_atime st_atim.tv_sec      /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};

測試 st_mode 的巨集:
S_ISREG(m)  is it a regular file?
S_ISDIR(m)  directory?
S_ISCHR(m)  character device?
S_ISBLK(m)  block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m)  symbolic link?  (Not in POSIX.1-1996.)
S_ISSOCK(m) socket?  (Not in POSIX.1-1996.)

測試檔案許可權的掩碼(與 st_mode 按位與來判斷):
S_ISUID     04000   set-user-ID bit
S_ISGID     02000   set-group-ID bit (see below)
S_ISVTX     01000   sticky bit (see below)

S_IRWXU     00700   owner has read, write, and execute permission
S_IRUSR     00400   owner has read permission
S_IWUSR     00200   owner has write permission
S_IXUSR     00100   owner has execute permission

S_IRWXG     00070   group has read, write, and execute permission
S_IRGRP     00040   group has read permission
S_IWGRP     00020   group has write permission
S_IXGRP     00010   group has execute permission

S_IRWXO     00007   others (not in group) have read,  write,  and
		execute permission
S_IROTH     00004   others have read permission
S_IWOTH     00002   others have write permission
S_IXOTH     00001   others have execute permission

16 chmod檔案許可權

/*

#include <sys/stat.h>

修改檔案 pathname/fd 的模式為 mode
成功返回 0,失敗返回 -1
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);

 */

int main(int argc, char **argv)
{
	char *pathname;
	mode_t mode;

	if (3 != argc)
	{
		printf("用法:%s <檔案> <模式>\n", argv[0]);
		return -1;
	}
	pathname = argv[1];

	sscanf(argv[2], "%o", &mode);   //轉為八進位制

	if (-1 == chmod(pathname, mode))
	{
		perror("修改模式失敗");
		return -1;
	}
	
	return 0;
}

17_檔案屬性控制


#include <unistd.h>
#include <fcntl.h>

對檔案 fd 進行 cmd 操作
根據 cmd 情況傳遞不同型別的引數 arg
int fcntl(int fd, int cmd, ... /* arg */ );

返回值:
失敗返回 -1
成功除以下 cmd 之外,返回 0:
F_DUPFD  The new descriptor.
F_GETFD  Value of file descriptor flags.
F_GETFL  Value of file status flags.
F_GETLEASE Type of lease held on file descriptor.
F_GETOWN Value of descriptor owner.
F_GETSIG Value  of  signal sent when read or write becomes possible, or
zero for traditional SIGIO behavior.
F_GETPIPE_SZ, F_SETPIPE_SZ The pipe capacity.
F_GET_SEALS A bit mask identifying the seals that have been  
set  for  the inode referred to by fd.

記錄鎖的命令有:F_SETLK, F_SETLKW, F_GETLK
使用的資料型別為:
struct flock {
	...
		short l_type;    /* Type of lock: F_RDLCK,
							F_WRLCK, F_UNLCK */
	short l_whence;  /* How to interpret l_start:
						SEEK_SET, SEEK_CUR, SEEK_END */
	off_t l_start;   /* Starting offset for lock */
	off_t l_len;     /* Number of bytes to lock */
	pid_t l_pid;     /* PID of process blocking our lock
						(set by F_GETLK and F_OFD_GETLK) */
	...
};
可以鎖住檔案的一部分:基於 l_whence 偏移 l_start 位元組的 l_len 大小
鎖住整個檔案:l_whence = SEEK_SET, l_start = 0, l_len = 0

寫鎖:一個程序先加寫鎖成功,則其它程序無論讀還是寫鎖都不能加
讀鎖:一個程序先加讀鎖成功,其它程序加可以讀鎖,但不能加寫鎖
int main(int argc, char **argv)
{
	char *pathname;
	int fd, i;
	struct flock lock;
	char *str;

	if (3 != argc)
	{
		printf("用法:%s <檔案> <字串>\n", argv[0]);
		return -1;
	}
	pathname = argv[1];
	str = argv[2];

	fd = open(pathname, O_RDWR, 0644);
	if (-1 == fd)
	{
		perror("開啟檔案失敗");
		return -1;
	}

	// 獲取檔案記錄鎖
	if (-1 == fcntl(fd, F_GETLK, &lock))
	{
		perror("獲取記錄鎖失敗");
		goto _out;
	}

	printf("pid = %d\n", lock.l_pid);

	// 加記錄鎖才寫入
	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;
	while (-1 == fcntl(fd, F_SETLK, &lock))
	{
		perror("加記錄鎖失敗");
	}

	// 向檔案寫入資料
	for (i = 0; i < strlen(str); i++)
	{
		write(fd, str + i, 1);
		write(STDERR_FILENO, str + i, 1);
		usleep(200000);
	}
	putchar('\n');

	// 解記錄鎖
	lock.l_type = F_UNLCK;
	if (-1 == fcntl(fd, F_SETLK, &lock))
	{
		perror("解記錄鎖失敗");
		goto _out;
	}

_out:
	// 關閉檔案
	close(fd);

	return 0;
}