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;
}