特点与概念
linux中常见的文件类型如下:
普通文件 :磁盘文件,能够进行随即存取的数据存储单位,他是面向字节的
管道文件 :有两种类型的管道,有名管道和无名管道
目录文件 :保存在目录中的文件的列表
设备文件 :提供到物理设备的接口
符号链接 :包含到达另一个文件的路径的文件
套接口 :类似于管道,但是通信距离可以不再同一台机器上。
文件模式
文件访问和修饰的位掩码宏
S_ISUID 04000 set user ID on execution
S_ISGID 02000 set group ID on execution
S_ISVTX 01000 sticky bit
S_IRUSR 00400 read by owner
S_IWUSR 00200 write by owner
S_IXUSR 00100 execute/search by owner
S_IRGRP 00040 read by group
S_IWGRP 00020 write by group
S_IXGRP 00010 execute/search by group
S_IROTH 00004 read by others
S_IWOTH 00002 write by others
S_IXOTH 00001 execute/search by others
文件类型常量:
S_IFMT 00170000 所有文件类型
S_IFSOCK 00140000 套接口文件
S_IFLNK 00120000 符号连接
S_IFREG 00100000 普通文件
S_IFBLK 00060000 块设备
S_IFDIR 00040000 目录文件
S_IFCHR 00020000 字符设备
S_IFFIFO 00010000 FIFO文件
umask
在创建新的文件的时候,可以用umask屏蔽掉不需要的权限,其原型如下:
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
例如:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(void)
{
mode_t after = 0444; /*屏蔽掉所有的读的权限*/
mode_t before;
system("touch before"); /*touch命令所建立的文件是644的*/
before = umask(after);
printf("before : %#o\nafter : %#o\n",before, after);
system("touch after"); /*更改权限后,再次建立文件*/
system("ls before after -l");
system("rm -f before after");
return 0;
}
输出:
before : 022
after : 0444
--w--w--w- 1 gin vboxusers 0 08-27 10:14 after
-rw-r--r-- 1 gin vboxusers 0 08-27 10:14 before
在未调用umask之前的权限是644,之后的是222,很明显将所有的读的权限全部都去掉了。
文件描述符
文件描述符是一个很小的正整数,他是一个索引值,指向内核为每一个进程所维护的此进程打开的文件的记录表。例如:stdin是0,stdout是1,stderr是2,这是每个进程都会打开的,他们对应的宏是STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO。
使用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);
注意,这里open()有两种形式,pathname是你要打开的文件的名字,flags是以什么方去操作这个文件,只读,只写,或者是读写,如果按默认(umask())的文件模式就用地一种形式,即不指定mode,否则就使用第二种模式。如果成功就返回一个文件描述符,否则返回-1并设置errno变量。
flags:
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
O_CREAT 若文件不存在则创建该文件
O_EXCL 仅与O_CREAT连用,如果文件存在,则强制open失败
O_NOCTTY 如果打开的是一个终端,就不会成为打开其进程的控制终端。
O_TRUNC 如果文件存在,则将文件长度截至0
O_APPEND 将文件指针设置到文件结尾处(如果打开来写)
O_NONBLOCK 如果读操作没有blocking(由于某种原因被拖延)则无法完成时,读操作返回0字节
O_NODELAY 同上
O_SYNC 在数据被物理的写入磁盘或其他设备之后操作才返回
creat()也能打开一个文件,当文件不存在的时候,则创建他,成功返回一个文件描述符,失败返回-1,并设置errno变量。
关闭一个文件描述符的方法是调用close()函数,原型为:
#include <unistd.h>
int close (int fd);
调用该文件之后,该进程对文件fd所加的锁全部都会被释放,如果关闭文件倒是他的连接数为0,则该文件将会被删除。
例如:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
int main(void)
{
int fd;
if (0 > (fd = open("test", O_CREAT | O_RDONLY, 0644)))
{
perror("File test:");
return 0;
}
system("ls -l");
close(fd);
return 0;
}
输出为:
总计 36
-rwxr-xr-x 1 gin vboxusers 5073 08-27 10:55 a.out
-rw-r--r-- 1 gin vboxusers 293 08-27 10:55 open.c
-rw-r--r-- 1 gin vboxusers 0 08-27 10:54 test
-rw-r--r-- 1 gin vboxusers 542 08-27 10:14 umask.c
-rw------- 1 gin vboxusers 0 08-27 08:58 输入输出
文件描述符的读写操作
读操作:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
其中,fd是用open打开的文件描述符,buf是用来存放从fd读出的count字节的内容的存放的地方。如果read调用成功,就会返回读出的字节数,如果出错就会返回-1,并设置errno变量。但是,假如有一个文件有10个字节,我们每次都从里面读出5个的话,第二次读完之后,按理来说一共十个字节已经读完了,但他还是又多读了一次,这是为什么?谁能解释解释,是不是因为他没有读到EOF标志,就不算读完?当我再用fopen函数在去读的时候,就没有再多读一次,但是用feof函数却检测不出读完了,是不是feof也没遇到EOF,所以他会多读一次然后遇到EOF才设置EOF标志为?
写操作:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
将buf中的count字节写入到fd中。
例如:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fscr;
int fdst;
int size;
char scr[20];
char dst[20];
char buf[10];
puts("Input scr and dst file : \n");
scanf("%20s %20s",scr, dst);
if ((0 > (fscr = open(scr, O_RDONLY)))
|| (0 > (fdst = open(dst, O_WRONLY | O_CREAT,0644))
{
puts("File eror\n");
exit(EXIT_FAILURE);
}
while (size = read(fscr, buf, 10))
write(fdst, buf, size);
close(fscr);
close(fdst);
return 0;
}
上例中用read和write简单的完成了文件的拷贝工作,当然,这是一个不安全的版本。
改变文件大小:
ftruncate()函数,原型为:
#include <unistd.h>
int ftruncate (int fd, off_t length);
他可以将fd的长度固定到length大小,若fd本身的大小不足length则后面会填充一些东西,如果fd的长度超过了length,那么,fd的长度将会被截短到length的长度。当length的值为0的时候,该函数的就可以将fd清空,但是,文件在打开的时候必须以写的模式打开。函数调用成功后返回0,失败返回-1,并设置errno变量。
文件读写指针定位
函数lseek的作用和库函数fseek的作用一样,只是前者操作的是文件描述符后者操作的是文件指针。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
whence可以是下面三个宏中的任意一个:
SEEK_SET 表示以文件的开始为相对位置
SEEK_CUR 表示以当前指针的位置作为相对位置,此时的offset可以为负数
SEEK_END 表示以文件末尾为相对的位置
如果调用成功,则返回新的指针的位置,否则返回-1,并设置errno变量。
写入到硬盘
使用fsync函数可以将所有的已经写入文件描述符fd的数据写道硬盘或者其他设备上去,
#include <unistd.h>
int fsync(int fd);
#ifdef _POSIX_SYNCHRONIZED_IO
int fdatasync(int fd);
#endif
成功返回0,否则的返回-1,并设置errno变量。fdatasync和fsync类似,但不写入文件的索引节点信息,如修改时间等。
获得文件信息
#include <sys/stat.h>
#include <unistd.h>
int fstat (int fd, struct stat * buf);
将文件描述符fd的信息保存到buf中去,成功返回0,失败返回-1并设置errno变量。
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 blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
利用下面的一些宏可以方便的判断文件类型:
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.)
其中,参数m可以用stat中的st_mode的填入;
例如:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main(void)
{
int fd;
char f[50];
struct stat *buf = (struct stat *)malloc(sizeof (struct stat));
puts("Input a file:\n");
scanf("%s",f);
if (0 > (fd = open(f,O_RDONLY)))
{
perror("File failed\n");
exit(EXIT_FAILURE);
}
if (0 > fstat(fd, buf))
{
perror("test error\n");
exit(EXIT_FAILURE);
}
if (S_ISREG(buf->st_mode))
puts("It's a common file.\n");
else if (S_ISDIR(buf->st_mode))
puts("It's a direction file.\n");
else if (S_ISCHR(buf->st_mode))
puts("It's a character device.\n");
else if (S_ISBLK(buf->st_mode))
puts("It's a block device.\n");
else if (S_ISLNK(buf->st_mode))
puts("It's a link file.\n");
else if (S_ISFIFO(buf->st_mode))
puts("It's a FIFO file.\n");
else if (S_ISSOCK(buf->st_mode))
puts("It's a socket file.\n");
else
puts("Unkown\n");
close(fd);
return 0;
}
文件权限的更改
更改所有权:
#include <sys/types.h>
#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
该函数和命令的使用方法一样,成功返回0,否则返回-1并设置errno变量。
更改读写权限:
#include <sys/types.h>
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fildes, mode_t mode);
给文件上锁
#include <sys/file.h>
int flock(int fd, int operation);
请求或删除由文件描述符fd引用的文件上的一个建议性锁。其中operation的取值如下:
LOCK_SH 共享性锁
LOCK_EX 排斥性锁
LOCK_UN 解锁
LOCK_NB 于其他值进行或操作以防止上锁。
在任意时刻,只能有一个进程加排斥性锁,但可以有多个进程加共享性锁。由flock施加的锁不能和fcntl或者lockf加的锁进行通讯,也不能和/var/lock下的UUCP锁文件进行通信。
通常访问一个上锁文件的步骤如下:
1。检查是否锁
2。如果没有,则自己建立。
3。打开文件
4。对文件作必要的处理
5。关闭文件
6。对文件进行解锁
函数fcntl()
他能够建立记录锁,所谓的记录锁是指仅对文件的一部分加锁而不是整个文件,这样就提高了文件的有效利用率。同时,他还能够用于读取锁和写入锁,read lock也称为共享锁(shared lock),因为有多个进程能够在同一文件上或者在文件的同一部分建立shared lock。而write lock也称为排斥锁,因为只在任何时刻只能有一个进程在文件的某个部分加write lock。当然,在建立write lock的部分也不能在建立read lock,当然,fcntl函数的作用不但但是给文件加锁这么简单。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock * lock);
其中,cmd的作用使用来说明fcntl他应该做什么工作,他的取之如下:
F_DUPFD 复制文件描述符fd
F_GETFD 获得fd的close-on-exec标志,若标志没有设置,仍为0,则文件爱你经过exec系列调用之后,仍然保持打开状态
F_SETFD 设置close-on-exec标志,以便在arg中传送的值
F_GETFL 得到open设置的标志
F_SETFL 改变open设置的标志
F_GETLK 得到离散的文件锁
F_SETLK 设置获得离散的文件锁,不等待
F_SETLKW 设置获得离散的文件锁,在需要时,等待
F_GETOWN 检索将受到SIGIO和SIGURG信号的进程ID或者进程组号
F_SETOWN 设置进程ID或者进程组号
struct flock {
short l_type;
short l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
__ARCH_FLOCK_PAD
};
#define F_RDLCK 0
#define F_WRLCK 1
#define F_UNLCK 2
此时仅仅讨论文件加锁的问题,要设置锁,需要传递值为F_SETLK或者F_SETLKW,设置locl.l_type的值为F_RDLCK(read lock)或者F_WRLCK(用于写入锁),相反,要清除锁,则设置lock.l_type的值为F_UNLCK,成功设置之后,函数返回0,失败返回-1,并设置errno的值为EAGIN。要检查锁的状态,可以用F_GETLK,如果进程已经设置了锁,则会在flock结构中填满相关信息。否则,lock.l_type将会等于F_UNLCK,表明没有设置任何锁。
例如:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int setlock(int fd, int type);
int main(void)
{
int fd;
if (0 > (fd = open("test", O_CREAT | O_RDWR, 0666)))
{
perror("open file failed\n");
exit(EXIT_FAILURE);
}
printf("Process ID : %d\n", getpid());
puts("set a read lock\n");
setlock(fd,F_RDLCK);
puts("done\n");
getchar();
puts("unlock the file\n");
setlock(fd, F_UNLCK);
puts("done\n");
getchar();
puts("set a write lock\n");
setlock(fd, F_WRLCK);
puts("done\n");
getchar();
close(fd);
return 0;
}
int setlock(int fd, int type)
{
struct flock l;
l.l_whence = SEEK_CUR;
l.l_start = 0;
l.l_len = 1;
while (1)
{ /*这里是想不断的尝试设置锁,直到他成功*/
l.l_type = type;
if (fcntl(fd, F_SETLK, &l))
return 0;
fcntl(fd, F_GETLK, &l);
if (l.l_type == F_UNLCK)
continue;
switch (l.l_type)
{
case F_RDLCK :
printf("read lock setted by %d\n",l.l_pid);
break;
case F_WRLCK :
printf("write lock setted by %d\n",l.l_pid);
break;
}
sleep(3);
}
}
在这里main函数很简单,主要用来显示信息,而setlock函数作了决大多数工作,他将为fd加锁,当失败时,会去查找原因,并延时3秒钟后再次尝试设置,直到文件被成功的加上锁。当然如果你同时用两个终端来运行他,那么你就会更加明白其中的很多事情的。
文件描述符的复制dup和dup2函数
#include <unistd.h>
int dup (int oldfd);
int dup2 (int oldfd, int newfd);
复制一个现存的文件描述符,成功返回新的文件描述符,失败返回-1,在dup2函数中,若newfd已经打开,则会现将其关闭,若新旧一样的话,那么就不会关闭,而是返回该值。此时,新老文件描述符共享文件偏移量,标志,和锁,但是,他们并不共享close-on-exec标志。例如:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
int main(void)
{
int ofd;
int nfd;
char *t1 = "1234567890";
char *t2 = "9876543210";
if (0 > (ofd = open("test",O_CREAT | O_WRONLY)))
{
perror("open test failed\n");
exit(EXIT_FAILURE);
}
write(ofd, t1, 10);
nfd = dup(ofd);
write(nfd, t2, 10);
close(ofd);
close(nfd);
return 0;
}
该程序,首先创建一个文件,然后用旧的文件描述符想里面写一段数字,然后用dup函数复制该文件描述符,然后用新的文件描述符向里面写了一段数字,当程序结束的时候,打开test文件,就会发现,刚才写的两段文字都被放到了test文件中。
dup2的作用常用来作重定向的工作,也就是说,本来应该对newfd的做得事情,被转移到oldfd上来作,例如,将stdout重定向到一个文件中去,这样,在屏幕上输出的将会被写到文件里去。例如:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd;
char *buf = "123456789";
if (0 > (fd = open("test", O_CREAT | O_WRONLY, 0644)))
{
perror("open test failed\n");
exit(EXIT_FAILURE);
}
dup2(fd, STDOUT_FILENO);
printf("%s",buf);
close(fd);
return 0;
}
该程序将标准输出重定向到test文件中,也就是说,在程序中使用想printf函数这样对标准输出文件操作的函数,都将会转向对test的操作。
同时读写多个文件
系统调用select函数可以实现多个文件的同时读写工作,这种事尤其是在服务器程序上面的应用十分广泛,因为他们要处理很多的文件,如果一个一个处理的话,效率会很低。其原型如下:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
其中,nfds通常是在受监视的集合中文件描述符最大的的加1的值。readfds是用来读取数据的集合,writefds是用来写入数据的集合,exceptfds是文件异常的集合。而最后一个参数timeout表示将会阻塞多久。如果timeout设置为0,则函数会立即返回,也就是非阻塞模式,当想等待到有情况发生,比如readfds/writefds发生改变,或者出现了错误,若想在这种情况下在返回,就直接传入NULL,如果函数成功,将返回受监视的文件描述符集合中包含的文件描述符的个数,或者返回0表示,在函数返回前没有描述符改变状态。如果函数调用出错,就会返回-1,并设置errno变量。
另外,
FD_ZERO(fd_set *set); 清除集合set
FD_SET(int fd, fd_set *set); 把fd添加到set
FD_CLR(int fd, fd_set *set); 把fd从set中删除
FD_ISSET(int fd, fd_set *set);判断fd是否在set中。
用来处理描述符集合。