1 文件描述符
内核为每个进程维护一个已打开文件的记录表(实现为结构体数组),文件描述符是一个较小的正整数(0—1023)(结构体数组下标),它代表记录表的一项,通过文件描述符和一组基于文件描述符的文件操作函数,就可以实现对文件的读、写、创建、删除等操作。
常用基于文件描述符的函数有open(打开)、creat(创建)、close(关闭)、read(读取)、write(写入)、ftruncate(改变文件大小)、lseek(定位)、fsync(同步)、fstat(获取文件状态)、fchmod(权限)、flock(加锁)、fcntl(控制文件属性)、dup(复制)、dup2、select和ioctl。基于文件描述符的文件操作并非ANSI C的函数。
2 打开、创建和关闭文件
open和creat都能打开和创建函数,原型为:
#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);//文件名 打开方式 权限 int creat(const char *pathname, mode_t mode); //文件名 权限 //现在已经不常用了 creat函数等价于open(pathname,O_CREAT|O_TRUNC|O_WRONLY,mode);
open()函数出错时返回-1,相关参数如下:
flags和mode都是一组掩码的合成值,flags表示打开或创建的方式,而mode表示文件的访问权限。
flags的可选项如下:
掩码 |
含义 |
O_RDONLY |
以只读的方式打开 |
O_WRONLY |
以只写的方式打开 |
O_RDWR |
以读写的方式打开 |
O_CREAT |
以写方式打开文件,如果文件不存在,则创建文件(注意要指定权限) |
O_EXCL |
仅与O_CREAT连用,如果文件已存在,则强制open失败 |
O_TRUNC |
如果文件存在,将文件的长度截至0,即将所有内容清空。(不加O_TRUNC的话,从文件开头从前往后覆盖,原文件没被覆盖的内容仍然存在) |
O_APPEND |
已追加的方式打开文件,每次调用write时,文件指针自动先移到文件尾,用于多进程写同一个文件的情况。 |
O_NONBLOCK |
非阻塞方式打开,无论有无数据读取或等待,都会立即返回进程之中。 |
O_NODELAY |
非阻塞方式打开 |
O_SYNC |
同步打开文件,只有在数据被真正写入物理设备设备后才返回 |
mode的可选项有:
S_IRWXU 00700 权限,代表该文件所有者具有可读、可写及可执行的权限。
S_IRUSR 或S_IREAD,00400权限,代表该文件所有者具有可读取的权限。
S_IWUSR 或S_IWRITE,00200权限,代表该文件所有者具有可写入的权限。
S_IXUSR 或S_IEXEC,00100权限,代表该文件所有者具有可执行的权限。
S_IRWXG 00070权限,代表该文件用户组具有可读、可写及可执行的权限。
S_IRGRP 00040权限,代表该文件用户组具有可读的权限。
S_IWGRP 00020权限,代表该文件用户组具有可写入的权限。
S_IXGRP 00010权限,代表该文件用户组具有可执行的权限。
S_IRWXO 00007权限,代表其他用户具有可读、可写及可执行的权限。
S_IROTH 00004权限,代表其他用户具有可读的权限
S_IWOTH 00002权限,代表其他用户具有可写入的权限。
S_IXOTH 00001 权限,代表其他用户具有可执行的权限。
但是通常采用直接赋数值的形式,如:
int fd = open(“1.txt”,O_WRONLY | O_CREAT,0755); //表示给755的权限 if(-1 == fd) { perror("open failed! "); exit(-1);
}
注意:LINUX中基于文件描述符的open函数,对于一个不存在的文件,不能通过O_WRONLY的方式打开,必须加上O_CREAT选项。
close用于文件的关闭:
int close(int fd);//fd表示文件描述词,是先前由open或creat创建文件时的返回值。
文件使用完毕后,应该调用close关闭它,一旦调用close,则该进程对文件所加的锁全都被释放,并且使文件的打开引用计数减1,只有文件的打开引用计数变为0以后,文件才会被真正的关闭。
Kris_示例代码_1
/************************************************************************* > File Name: my_open.c > Author: KrisChou > Mail:zhoujx0219@163.com > Created Time: Mon 18 Aug 2014 10:27:49 AM CST ************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(int argc, char *argv[]) { /* 打开文件 int fd; //描述符 fd = open(argv[1],O_RDONLY); if(fd == -1) { perror("open failed"); exit(1); } return 0; */ int fd; fd = open(argv[1],O_WRONLY | O_CREAT,0666); if(fd == -1) { perror("open failed"); exit(1); } return 0; }
3 读写文件
读写文件的函数原型为:
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);//文件描述词 缓冲区 长度 ssize_t write(int fd, const void *buf, size_t count);
对于read和write函数,出错返回-1,读取完了之后,返回0,其他情况返回读写的个数。
Kris_示例代码_2
/************************************************************************* > File Name: my_read.c > Author: KrisChou > Mail:zhoujx0219@163.com > Created Time: Mon 18 Aug 2014 11:11:48 AM CST ************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main(int argc, char *argv[]) { int fd;//描述符 char buf[128]; int readn; fd = open(argv[1],O_RDONLY); if(fd == -1) { perror("open failed"); exit(1); } while(memset(buf,0,128)) { readn = read(fd,buf,127); printf("readn : %d : %s ",readn,buf); if(readn == 0) { printf("read end of file! "); break; } } close(fd); return 0; }
Kris_示例代码_3
/************************************************************************* > File Name: my_write.c > Author: KrisChou > Mail:zhoujx0219@163.com > Created Time: Mon 18 Aug 2014 11:35:36 AM CST ************************************************************************/ #include <stdio.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { int fd; int fd_2; char buf[128]; int read_number; int write_number; fd = open(argv[1],O_RDONLY); fd_2 = open(argv[2],O_WRONLY | O_CREAT, 0666); if(fd == -1 || fd_2 == -1) { perror("open failed"); exit(1); } while(memset(buf,0,128)) { read_number = read(fd,buf,127); printf("read_number: %d ",read_number); if(read_number == 0) { printf("read end of file! "); break; } write_number = write(fd_2,buf,read_number); printf("write: %d ",write_number); } close(fd); close(fd_2); }
4 获取文件信息
可以通过fstat和stat函数获取文件信息,调用完毕后,文件信息被填充到结构体struct stat变量中,函数原型为:
#include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int stat(const char *file_name, struct stat *buf); //文件名 stat结构体指针 int fstat(int fd, struct stat *buf); //文件描述词 stat结构体指针
结构体stat的定义为:
struct stat { dev_t st_dev; /*如果是设备,返回设备表述符,否则为0*/ ino_t st_ino; /* i节点号 */ mode_t st_mode; /* 文件类型 */ nlink_t st_nlink; /* 链接数 */ uid_t st_uid; /* 属主ID */ gid_t st_gid; /* 组ID */ dev_t st_rdev; /* 设备类型*/ off_t st_size; /* 文件大小,字节表示 */ blksize_t st_blksize; /* 块大小*/ blkcnt_t st_blocks; /* 块数 */ time_t st_atime; /* 最后访问时间*/ time_t st_mtime; /* 最后修改时间*/ time_t st_ctime; /* 创建时间 */ };
对于结构体的成员st_mode,有一组宏可以进行文件类型的判断
宏 |
描述 |
S_ISLNK(mode) |
判断是否是符号链接 |
S_ISREG(mode) |
判断是否是普通文件 |
S_ISDIR(mode) |
判断是否是目录 |
S_ISCHR(mode) |
判断是否是字符型设备 |
S_ISBLK(mode) |
判断是否是块设备 |
S_ISFIFO(mode) |
判断是否是命名管道 |
S_ISSOCK(mode) |
判断是否是套接字 |
同样有一组宏可以进行文件权限的判断
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IXUSR 00100 owner has execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
实例
如何编写 ls –l
思路
在struct stat中,文件所有者和组是以ID的形式存在的,然而ls要求输出用户名和组名,如何根据ID找到用户名和组名呢?
可以试着在联机帮助中查找关键字username、uid、group,看看又什么结果。不同的系统中可能有不同的结果。下面是一些说明:
1 /etc/passwd包含用户列表
回想一下登录过程,输入用户名和密码,经过验证后登录成功,出现提示符。那么系统是怎么知道用户名和密码是正确的呢?
这就涉及到/etc/passwd这个文件,它包含了系统中所有的用户信息,下面是一个例子:
root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown purple:x:500:500:purple:/home/purple:/bin/bash newUsr1:x:501:501::/home/newUsr1:/bin/bash test_user_3:x:502:502::/home/test_user_3:/bin/bash
这是个纯文本文件,每一行代表一个用户,用冒号“:”分成不同的字段,第一个字段是用户名,第二个字段是密码,第三个字段是用户ID,第四个字段是所属的组,接下来的是用户的全名、主目录以及登录shell。所有的用户对这个文件都有读权限。
似乎使用这个文件就可以解决用户ID和用户名的关联问题,只需搜索用户ID,然后就可以得到相应地用户名。然而在实际应用中并不是这样做的,搜索文件是一件很繁琐的工作,而且对于很多网络计算系统,这种方法是不起作用的。
2 /etc/passwd并没有包含所有的用户
每个Unix系统都有/etc/passwd这个文件,但它并没有包括所有的用户,在有些网咯计算系统中,用户需要能够登录到系统中的任何一台主机上,如果通过/etc/passwd来实现,就必须在所有主机上维护用户信息,要修改密码的话也必须修改所有主机上的密码,如果有一台主机刚好宕机了,那么在宕机期间的用户变化情况就无法同步到这台主机上。
较好的解决方法是在一台大家都能够访问到的主机上保存所有用户信息,它被称作NIS,所有的主机通过NIS来进行用户身份验证。所有需要用户信息的程序也从NIS上获取。而本地只保存所有用户的一个子集以备离线操作。
3 通过getpwuid来得到完整的用户列表
可以通过库函数getpwuid来访问用户信息,如果用户信息保存在/etc/passwd中,那么getpwuid会查找/etc/passwd的内容,如果用户信息在NIS中,getpwuid会从NIS中获取信息,所以用getpwuid使程序有很好的可移植性。
getpwuid需要UID(user ID)作为参数,返回一个指向struct passwd结构体的指针,这个结构定义在/usr/include/pwd.h中:
The passwd structure is defined in <pwd.h> as follows: struct passwd { char *pw_name; /* username */ char *pw_passwd; /* user password */ uid_t pw_uid; /* user ID */ gid_t pw_gid; /* group ID */ char *pw_gecos; /* real name */ char *pw_dir; /* home directory */ char *pw_shell; /* shell program */ };
tips: getpwuid() functions return a pointer to a passwd structure, or NULL if the matching entry is not found or an error occurs.
而这正是ls –l 的输出中需要的:
/** * return a username associated with the specified uid * NOTE: doesn't work if there is no username */ char *uid_to_name(uid_t uid) { return getpwuid(uid)->pw_name; }
接下来我们来讨论组ID如何处理。
4 /etc/group是组的列表
对一台公司的主机而言,可能要将用户分为不同的组,如销售人员一组、行政人员一组等。要是在学校里,可能有教师组和学生组。Unix提供了进行组管理的手段,文件/etc/group是一个保存所有的组信息的文本文件:
root:x:0:root bin:x:1:root,bin,daemon daemon:x:2:root,bin,daemon sys:x:3:root,bin,adm adm:x:4:root,adm,daemon tty:x:5: disk:x:6:root lp:x:7:daemon,lp purple:x:500: newUsr1:x:501: zjxgroup:x:502:
其中第一个字段是组名,第二个字段是组密码,这个字段极少用到,第三个字段是组ID(GID),第四个是组中的成员列表。
5 用户可以同时属于多个组
passwd文件中有每个用户所属的组,实际上那里列出的是用户的主组(primary group)。用户还可以是其他组的成员,要将用户添加到组中,只要把它的用户名添加到/etc/group中这个组所在行的最后一个字段即可。这个列表在处理访问权限时会被用到。例如一个文件属于sys组,且组成员有这个文件的写权限,则用户adm就可以修改这个文件。
6 通过getgrgid来访问组列表
在网络计算系统中,组信息也被保存在NIS中。与getpwuid类似,Unix系统提供getgrgid函数屏蔽掉实现的差异。用这个函数,用户可以得到组名而不用操心实现的细节。
The group structure is defined in <grp.h> as follows: struct group { char *gr_name; /* group name */ char *gr_passwd; /* group password */ gid_t gr_gid; /* group ID */ char **gr_mem; /* group members */ };
tips: getgrgid() functions return a pointer to a group structure, or NULL if the matching entry is not found or an error occurs.
在ls –l 中,可以这样得到组名:
/** * returns a groupname associated with the specified gid * NOTE: doesn't work if there is no groupname */ char* gid_to_name(gid_t gid) { return getgrgid(gid)->gr_name; }
kris_示例代码_4
/************************************************************************* > File Name: my_stat.c > Author: KrisChou > Mail:zhoujx0219@163.com > Created Time: Mon 18 Aug 2014 09:45:09 PM CST ************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <string.h> #include <time.h> #include <pwd.h> #include <grp.h> void mode_to_str(mode_t mode, char *dest); char *time_format(char *src); int main(int argc, char *argv[]) { struct stat my_stat; char buf[11] = ""; char *pstr; memset(&my_stat,0,sizeof(my_stat)); if(stat(argv[1], &my_stat) == -1) { perror("stat:"); exit(1); } printf("ino: %u ", my_stat.st_ino); mode_to_str(my_stat.st_mode, buf); printf("mode: %s ",buf); printf("nlink: %u ", my_stat.st_nlink); printf("uid: %s, gid: %s ",getpwuid(my_stat.st_gid)->pw_name,getgrgid(my_stat.st_gid)->gr_name); printf("size: %u ",my_stat.st_size); pstr = ctime(&(my_stat.st_atime)); //Mon Aug 18 09:45:09 2014 pstr = time_format(pstr); printf("atime: %s ",pstr); printf("------------------------------------------------------------ "); printf("%10s.%2d%7s%7s%5d%13s %s ",buf, my_stat.st_nlink, getpwuid(my_stat.st_uid)->pw_name, getgrgid(my_stat.st_gid) ->gr_name, my_stat.st_size, pstr,argv[1]); return 0; } void mode_to_str(mode_t mode, char* dest) { memset(dest,'-',10); printf("dest: %s ", dest);//---------- if(S_ISDIR(mode)) { dest[0]='d'; } if(S_ISREG(mode)) { dest[0]='-'; } if(mode & S_IRUSR) { dest[1] = 'r' ; } if(mode & S_IWUSR) { dest[2] = 'w' ; } if(mode & S_IXUSR) { dest[3] = 'x' ; } if(mode & S_IRGRP) { dest[4] = 'r' ; } if(mode & S_IWGRP) { dest[5] = 'w' ; } if(mode & S_IXGRP) { dest[6] = 'x' ; } if(mode & S_IROTH) { dest[7] = 'r' ; } if(mode & S_IWOTH) { dest[8] = 'w' ; } if(mode & S_IXOTH) { dest[9] = 'x' ; } } char* time_format(char* src) { int index = strlen(src) - 1 ; for(; src[index]!=':'; index -- ) { } src[index] = '