Linux 程序设计学习笔记----POSIX 文件及目录管理

转载请注明:http://blog.csdn.net/suool/article/details/38141047

问题引入

文件流和文件描述符的区别

上节讲到ANSI C 库函数的实现在用户态,流的相应资源也在用户空间,但无论如何实现最终都需要通过内核实现对文件的读写控制.因此fopen函数必然调用了对OS的系统调用.这一调用在LINUX下即为open, close, read, write等函数.这些都遵循POSIX标准.

so,在linux系统中是如何通过POSIX标准实现对文件的操作和目录的管理的呢?

问题解析

linux系统对文件结构的定义如下:fs.h头文件

查看完整文件,请访问:http://www.cs.fsu.edu/~baker/devices/lxr/http/source/linux/include/linux/fs.h

struct file {
906         /*
907          * fu_list becomes invalid after file_free is called and queued via
908          * fu_rcuhead for RCU freeing
909          */
910         union {
911                 struct list_head        fu_list;
912                 struct rcu_head         fu_rcuhead;
913         } f_u;
914         struct path             f_path;
915 #define f_dentry        f_path.dentry
916 #define f_vfsmnt        f_path.mnt
917         const struct file_operations    *f_op;
918         spinlock_t              f_lock;  /* f_ep_links, f_flags, no IRQ */
919         atomic_long_t           f_count;
920         unsigned int            f_flags;
921         fmode_t                 f_mode;
922         loff_t                  f_pos;
923         struct fown_struct      f_owner;
924         const struct cred       *f_cred;
925         struct file_ra_state    f_ra;
926 
927         u64                     f_version;
928 #ifdef CONFIG_SECURITY
929         void                    *f_security;
930 #endif
931         /* needed for tty driver, and maybe others */
932         void                    *private_data;
933 
934 #ifdef CONFIG_EPOLL
935         /* Used by fs/eventpoll.c to link all the hooks to this file */
936         struct list_head        f_ep_links;
937 #endif /* #ifdef CONFIG_EPOLL */
938         struct address_space    *f_mapping;
939 #ifdef CONFIG_DEBUG_WRITECOUNT
940         unsigned long f_mnt_write_state;
941 #endif
942 };

文件描述符&文件结构

而对于用户空间来说,任何打开的文件都将内分配一个非负数,用于表示这个被打开的文件,该值是文件描述符,因此任何进程在运行时候默认打开的三个流对象都有自己的文件描述符,对应为0,1,2.

标准文件的文件描述符定义如下:

下图是进程打开文件的内核数据结构:

文件流与文件描述符的转换

Linux为用户层提供了函数fileno()从文件流中读取其文件描述符,即获取struct FILE的_fileno成员,而fdopen()函数实现某个流与一个文件描述符相接.

函数声明如下:

POSIX标准下文件I/O 管理

首先看OS源代码对文件操作的定义:


打开文件,使用系统的open函数.

#include <fcntl.h>

int open(const char *path, int oflag, ... );
该函数的详细说明,参见:pubs.opengroup.org/onlinepubs/009695399/functions/open.html

该函数的第一个参数是要打开文件的路径,第二个参数是打开文件的方式,各个方式的定义如下:

关闭文件使用close函数

#include <unistd.h>

int close(int fildes);
详情参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/close.html

创建文件

除了在open函数中的参数O_CREATE,还可以显式的使用create函数,

#include <fcntl.h>

int creat(const char *path, mode_t mode);

第一个参数是创建文件的路径.,第二个参数是mode和umask一起决定的文件权限.成功返回创建的文件标识符,错误返回-1.

下面是一个示例程序.

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

int main(int argc,char *argv[])
{
	int fd_open,fd_open_create,fd_create;
	if((fd_open=open("/bin/ls",O_RDONLY))==-1)  // open a file existed
	{
		perror("open");
		exit(EXIT_FAILURE);
	}
	printf("the file's descriptor is:%d\n",fd_open);
	// open a file ,if not existed then create one which named temp1
	if((fd_open_create=open("./tmp1",O_CREAT|O_EXCL,0644))==-1)
	{
		perror("open");
	//	exit(EXIT_FAILURE);
	}
	printf("the tmp1 file descriptor is:%d\n",fd_open_create);

	if((fd_create=creat("./tmp2",0644))==-1)  // create temp2
	{
		perror("create");
		exit(EXIT_FAILURE);
	}
	printf("the tmp2 file descriptor is:%d\n",fd_create);

	close(fd_open);
	close(fd_create);
	close(fd_open_create);               // close file
	return 0;
}

结果如下:(第二个文件创建失败)......


文件控制fcntl

#include <fcntl.h>

int fcntl(int fildes, int cmd, ...);
第一个参数是要修改的文件描述符,第二个是cmd相应的操作.

常用的命令有:


具体各个参数的意义不再赘述,可以自己搜索.

读写文件内容

点击查看详细说明

read()函数

write()函数

文件定位

在对文件进行读写的时候,当打开文件时通常有一个读写位置,通常是指向文件的头部,若是以附加的的方式打开文件,则在文件你尾部.

lseek()函数

下面是一个示例:

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

char buf1[] = "1234567890\n";   // 临时需要写入的信息
char buf2[] = "ABCDEFGHIJ\n";
char buf3[] = "abcdefghij\n";

int main(int argc,char *argv[])
{
	int fd;
	if ( (fd = creat("/tmp/test",0644 )) < 0)   // 创建文件
	{
		perror("creat");
		exit(EXIT_FAILURE);
	}
	if (write(fd, buf1, 10) != 10)             // 从文件开始写
	{
		perror("write");
		exit(EXIT_FAILURE);
	}
	
	if(lseek(fd, 20, SEEK_SET) == -1)         // 从20位置写
	{
		perror("lseek");
		exit(EXIT_FAILURE);
	}
	if (write(fd, buf2, 10) != 10)           
	{
		perror("write");
		exit(EXIT_FAILURE);
	}
	
	if(lseek(fd, 10, SEEK_SET) == -1)         // 从10开始写
	{
		    perror("lseek");
			    exit(EXIT_FAILURE);
	}
	if (write(fd, buf3, 10) != 10)
	{
		    perror("write");
			    exit(EXIT_FAILURE);
	}
	return 0;
}

result:


锁定解锁文件

flock()函数和fcntl()都可以对文件进行锁定和解锁.但是前者只能锁定整个文件,而后者可以提供任意位置的文件锁定.

下面的示例程序是fcntl对文件锁定操作,在某个进程锁定的时期,另一个进程无法操作文件.

其中一个进程的代码如下:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
struct flock* file_lock(short type, short whence)
{
    static struct flock ret;
    ret.l_type = type ;
    ret.l_start = 0;
    ret.l_whence = whence;
    ret.l_len = 0;
    ret.l_pid = getpid();
    return &ret;
}
int main(int argc,char *argv[])
{
    int fd = open(argv[1], O_WRONLY|O_APPEND);
	int i;
	time_t now;
    for(i=0; i<1000; ++i) {
        fcntl(fd, F_SETLKW, file_lock(F_WRLCK, SEEK_SET));
		time(&now);
		printf("%s\t%s F_SETLKW  lock file %s for 5s\n",ctime(&now),argv[0],argv[1]);
        char buf[1024] = {0};
        sprintf(buf, "hello world %d\n", i);
        int len = strlen(buf);
        if(write(fd, buf, len))
			printf("%s\t%s write file sccess\n",ctime(&now),argv[0],argv[1]);
		sleep(5);	
        fcntl(fd, F_SETLKW, file_lock(F_UNLCK, SEEK_SET));
       sleep(1);
    }
    close(fd);
}

另一个代码与此类似,差别如下:

在一个终端运行一个进程如下:


目录流基本操作

本部分提到的函数的用法和上面的函数用法差不多,具体的需要请查阅源代码或文档使用.

1.打开关闭目录文件

函数opendor()

closedir()函数

2.读写目录内容

readdir()       // 多线程操作中不安全

readdir_r()   // 实现多线程读取目录内容

3.定位目录位置

telldir()类似与ftell()函数,返回目录相关联的当前的位置.

seekdir()类似与文件定位函数fseek()函数

rewinddir()函数类似与文件读写位置重置函数rewind()

4.添加和删除目录

mkdir()

rmdir()

5.当前工作路径操作

getcwd()获取当前路径

get_current_dir_name() 也是

修改当前目录:

chdir()   &&    fchdir()


案例应用

递归文件的目录复制操作

其中mian函数的流程图如下

程序的难点在于递归子目录,下面是递归子目录的流程图:

源代码如下:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <dirent.h>
#include <linux/types.h>
#include <fcntl.h>
#include <errno.h>

/*cp the link_file's src to dst*/
int cp_file(const char *src, const char *dst,mode_t mode)
{
	int fd_src,fd_dst;
	if(-1 == (fd_src =open(src,O_RDONLY)))
	{
		perror("open src");exit(EXIT_FAILURE);
	}
	
	if(-1 == (fd_dst =open(dst,O_WRONLY|O_TRUNC|O_CREAT,mode)))
	{
			perror("open dst");exit(EXIT_FAILURE);
	}
	
	int len=0;
	do
	{
		char buf[1024];
		len=read(fd_src,buf,1024);
		write(fd_dst,buf,len);
	}while(len>0);
	
	close(fd_src);
	close(fd_dst);
}


int cp_dir(const char *src,const char *dst)
{
	DIR *dirp = NULL;
	
	if(NULL== (dirp=opendir(src)))
	{
		perror("opendir");exit(EXIT_FAILURE);
	}	
	
	struct dirent *entp = NULL;
	while ( NULL != (entp =readdir(dirp)))
	{
		if (strcmp(entp->d_name, "..")==0 || strcmp(entp->d_name, ".")==0)	//ignore ./ ../
        {
          continue;
        }
		
		char *name_src =(char *) malloc( strlen(src) + 1 + strlen(entp->d_name) + 1 );
		sprintf(name_src,"%s/%s\0",src,entp->d_name);
		char *name_dst =(char *) malloc( strlen(dst) + 1 + strlen(entp->d_name) + 1 );
		sprintf(name_dst,"%s/%s\0",dst,entp->d_name);

		struct stat stat_src;
		if (stat(name_src, &stat_src) == -1) 
		{
			fprintf(stderr, "%s(%d): stat error(%s)!\n", __FILE__, __LINE__, strerror(errno));
			exit(EXIT_FAILURE);
		}	
	
		if (S_ISREG(stat_src.st_mode))		//regular file
		{
			cp_file(name_src,name_dst,stat_src.st_mode);
			free(name_src);
			free(name_dst);
		}	
		else if ( S_ISDIR( stat_src.st_mode ))
		{		
			if( -1 ==mkdir(name_dst,stat_src.st_mode))
			{
				perror("mkdir");exit(EXIT_FAILURE);
			}
			cp_dir( name_src, name_dst);		
			free(name_src);
			free(name_dst);
		}
	}
}


int main(int argc,char *argv[])
{
	if (argc < 3)
	{
		fprintf(stderr,"usage %s src_dir dst_src\n",argv[0]);exit(EXIT_FAILURE);
	}
	
	struct stat stat_src;
	if (stat(argv[1], &stat_src) != 0) 
	{
      fprintf(stderr, "%s(%d): stat error(%s)!\n", __FILE__, __LINE__, strerror(errno));
      exit(EXIT_FAILURE);
    }	
	
	umask(0000);
	if (S_ISREG(stat_src.st_mode))		//regular file
    {
		struct stat stat_dst;
		if (stat(argv[2], &stat_dst) == -1) 
		{
			if(errno != ENOENT)			//if errno not cause by file/dir not exist
			{
				fprintf(stderr, "%s(%d): stat error(%s)!\n", __FILE__, __LINE__, strerror(errno));
				exit(EXIT_FAILURE);
			}
			else						//if dst_flie not exist.
			{
				cp_file(argv[1],argv[2],stat_src.st_mode);
			}
		}	
		else		//dst file exist.
		{
			if(S_ISDIR(stat_dst.st_mode))	//cp a file to a exist dir
			{				
				char *ptr=(char *)malloc(strlen(argv[2])+1+strlen(argv[1])+1);
				sprintf(ptr,"%s/%s\0",argv[2],argv[1]);			
				cp_file(argv[1],ptr,stat_src.st_mode);
			}
			else						//cp file to a exist file
			{
				printf("file %s exist, do you want overwrite it[y/n]:",argv[2]);
				char ch;
				while(!scanf("%c",&ch))
				{
					getchar();
				}
				if(ch =='Y' || ch == 'y' )
				{
					unlink(argv[2]);
					cp_file(argv[1],argv[2],stat_src.st_mode);	
				}
				else
					return 1;	
			}
		}
    }	
	
	else if (S_ISDIR(stat_src.st_mode))	//dir
    {
		struct stat stat_dst;
		if (stat(argv[2], &stat_dst) == -1) 
		{
			if(errno != ENOENT)			//if errno not cause by file/dir not exist
			{
				fprintf(stderr, "%s(%d): stat error(%s)!\n", __FILE__, __LINE__, strerror(errno));
				exit(EXIT_FAILURE);
			}
			else						//file/dir not exist
			{
				errno=0;
				if(-1 == mkdir(argv[2],stat_src.st_mode))
				{
					perror("mkdir");exit(EXIT_FAILURE);
				}
				cp_dir(argv[1],argv[2]);
			}
		}
		else if(S_ISREG(stat_dst.st_mode))		//can't copy a dir to a file
		{
			fprintf(stderr,"can't copy a dir to a file\n");exit(EXIT_FAILURE);
		}
		else								//copy a dir to a exsit dir ,
		{			
			char *ptr=(char *)malloc(strlen(argv[1])+1+strlen(argv[2])+1);
			sprintf(ptr,"%s/%s\0",argv[2],argv[1]);
			if(-1 == mkdir(ptr,stat_src.st_mode))
			{
				perror("mkdir");exit(EXIT_FAILURE);
			}			
			cp_dir(argv[1],ptr);
			free(ptr);
		}
	}
	
}	

运行结果如下:


显示成功复制.


Next:

普通文件/链接文件/目录文件---属性管理

终端以及串口编程


转载请注明:http://blog.csdn.net/suool/article/details/38141047



Linux 程序设计学习笔记----POSIX 文件及目录管理,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。