Linux 中对目录和设备的操作都是文件操作,文件分为普通文件,目录文件,链接文件和设备文件。在Linux中对文件的操作,是使用文件指针来访问文件的方法是由标准 C 规定的,基于文件指针的文件操作函数是 ANSI 标准函数库的一部分。所以本次练习基本是复习C语言操纵文件的功能。其中mmap的内容先按下不表,讲完目录操作后继续。
#include //头文件包含
FILE* fopen(const char* path, const char* mode);//文件名 模式
int fclose(FILE* stream);
fopen 创建的文件的访问权限将以 0666 与当前的 umask 结合来确定。
下面案例中使用了wb方式创建了文件,并且关闭文件。
#include
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *stream);
fread 从文件流 stream 中读取 nmemb 个元素,写到 ptr 指向的内存中,每个元素的大小为 size 个字节
fwrite 从 ptr 指向的内存中读取 nmemb 个元素,写到文件流 stream 中,每个元素 size 个字节
#include
int printf(const char *format, ...);
//相当于 fprintf(stdout,format,…);
int scanf(const char *format, …);
int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, …);
int sprintf(char *str, const char *format, ...);
//eg:sprintf(buf,”the string is;%s”,str);
int sscanf(char *str, const char *format, …);
f 开头和s 开头的区别:
fprintf 将格式化后的字符串写入到文件流 stream 中
sprintf 将格式化后的字符串写入到字符串 str 中
注意fopen的参数需要改成wb+,支持读和写,否则什么都读不出来,作者测了半小时才发现是这个问题…
#include
int fgetc(FILE *stream);
int fputc(int c, FILE *stream);
int getc(FILE *stream);//等同于 fgetc(FILE* stream)
int putc(int c, FILE *stream);//等同于 fputc(int c, FILE* stream)
int getchar(void);//等同于 fgetc(stdin);
int putchar(int c);//等同于 fputc(int c, stdout);
char *fgets(char *s, int size, FILE *stream);
int fputs(const char *s, FILE *stream);
int puts(const char *s);//等同于 fputs(const char *s,stdout);
char *gets(char *s);//等同于 fgets(const char *s, int size, stdin);
#include
int feof(FILE * stream);
//通常的用法为 while(!feof(fp)),没什么太多用处
int fseek(FILE *stream, long offset, int whence);
//设置当前读写点到偏移 whence 长度为 offset 处
long ftell(FILE *stream);
//用来获得文件流当前的读写位置
void rewind(FILE *stream);
//把文件流的读写位置移至文件开头 fseek(fp, 0, SEEK_SET);
POSIX标准支持另一类不带缓冲区的IO。使用文件描述符描述文件,文件描述符是一个非0整数。原理上来说,每次打开文件,进程地址空间内核部分会维护一个已经打开的文件的数组,文件描述符就是这个数组的索引。因此文件描述符可以实现进程和打开文件之间的交互。
使用open函数可以打开或者创建一个并打开一个文件,使用creat函数可以创建一个文件
#include //头文件#include #include 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);#include int close(int fd);//fd 表示文件描述词,是先前由 open 或 creat 创建文件时的返回值。
从上述man手册中可以看到,函数的返回值都是int类型,也就是说,函数执行成功后会返回一个文件描述符,表示已经打开的文件;执行失败会返回-1,并设置相应的errno。flags表示打开或创建的方式,mode表示文件的访问权限。
掩码 | 含义 |
---|---|
O_RDONLY | 以只读的方式打开 |
O_WRONLY | 以只写的方式打开 |
O_RDWR | 以读写的方式打开 |
O_CREAT | 如果文件不存在,则创建文件 |
O_EXCL | 仅与 O_CREAT 连用,如果文件已存在,则 open 失 |
O_APPEND | 已追加的方式打开文件,每次调用 write 时,文件指针自动先移到文件尾,用于多进程写同一个文件的情况。 |
O_NONBLOCK | 非阻塞方式打开,无论有无数据读取或等待都会立即返回进程之中 |
O_NODELAY | 非阻塞方式打开 |
O_SYNC | 同步打开文件,只有在数据被真正写入物理设备设备后才返回 |
文件使用后,要记得使用close关闭文件。close关闭后,该进程队文件所加的锁全部被释放,并且是文件的打开索引计数-1,只有文件的打开引用计数变为0后,文件才会被真正的关闭。使用ulimit -a 命令可以查看单个进程能同事打开文件的上限。下面展示open函数创建文件的操作,其他函数和参数读者自行尝试。
使用read函数和write函数,他们统称为不带有缓冲区的IO。读取完了返回0,出错返回-1,其余情况返回读写的字符数。
#include
ssize_t read(int fd, void *buf, size_t count);//文件描述符 缓冲区 长度
ssize_t write(int fd, const void *buf, size_t count);
注意: 作者在测试读写案例时发现file1文件忘记加权限导致,这个案例花费了好久,读者可以自行修改上面的main.c源文件,添加权限参数,也可以使用如下命令修改文件权限。
修正案例如下:
ftruncate 函数可以改变文件大小,必须以写入模式 打开文件,如果文件大小比参数length大,就会删除超过的部分(实际上是修改了文件的inode信息)。成功返回0,否则返回-1;
#include
int ftruncate(int fd, off_t length);
#include
void *mmap(void *adr, size_t len, int prot, int flag, int fd, off_t offset);
adr参数用于指定映射存储区的起始地址。设为NULL,由操作系统自动分配(通常在堆空间)。fd参数是一个文件描述符,必须是打开的状态。prot参数表示权限,PROT_READ,PROT_WRITE 表示可读可写,flag表示这片空间是否可以反映到磁盘上的参数,MAP_SHARED、MAP_PRIVATE。offset参数需是 4k 的整数倍。
#include
#include
off_t lseek(int fd, off_t offset, int whence);//fd 文件描述词
//whence 可以是下面三个常量的一个
//SEEK_SET 从文件头开始计算
//SEEK_CUR 从当前指针开始计算
//SEEK_END 从文件尾开始计算
#include
#include
#include
int stat(const char *file_name, struct stat *buf); //文件名 stat 结构体指针
int fstat(int fd, struct stat *buf); //文件描述词 stat 结构体指针struct stat
{ dev_t st_dev; /* ID of device containing file -文件所在设备的ID*/ ino_t st_ino; /* inode number -inode节点号*/ mode_t st_mode; /* protection -保护模式?*/ nlink_t st_nlink; /* number of hard links -链向此文件的连接数(硬连接)*/ uid_t st_uid; /* user ID of owner -user id*/ gid_t st_gid; /* group ID of owner - group id*/ 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 - */
};
同时对于struct stat结构体st_node,有一组宏可以进行文件类型的判断:
宏 | 描述 |
---|---|
S_ISLNK(mode) | 判断是否是符号链接 |
S_ISREG(mode) | 判断是否是普通文件 |
S_ISDIR(mode) | 判断是否是目录 |
S_ISCHR(mode) | 判断是否是字符型设备 |
S_ISBLK(mode) | 判断是否是块设备 |
S_ISFIFO(mode) | 判断是否是命名管道 |
S_ISSOCK(mode) | 判断是否是套接字 |
#include
int dup(int oldfd);
int dup2(int oldfd, int newfd);
int fd = open(argv[1],O_RDWR);
int fd1 = fd;
close(fd);//会导致文件关闭
char buf[128] = {0};
int ret = read(fd1, buf, sizeof(buf)); //读取失败
#include
int main(int argc, char *argv[])
{ARGS_CHECK(argc,2);int fd = open(argv[1],O_RDWR);ERROR_CHECK(fd,-1,"open");printf("\n");close(STDOUT_FILENO);int fd1 = dup(fd);printf("fd1 = %d\n", fd1);close(fd);printf("the out of stdout\n");return 0;
}
printf("fd = %d\n", fd);
char buf[128] = {0};
read(fd, buf, 5);
printf("buf = %s\n", buf);
//使用 read 接口也是能够正常读取内容的
#include
FILE *fdopen(int fd, const char *mode);
FILE* fp = fopen(argv[1],"rb+");
close(fileno(fp));//如果使用 fd=fileno(fp),那么 close 以后 fd 的数值不会发生改变
//出现报错 fgets: Bad file descriptor
传输方式 | 含义 |
---|---|
全双工 | 双方可以同时向另一方发送数据 |
半双工 | 某个时刻只能有一方向另一方发送数据,其他时刻的传输方向可以相反 |
单工 | 永远只能由一方向另一方发送数据 |
$ mkfifo [管道名字]
使用 cat 打开管道可以打开管道的读端
$ cat [管道名字]
打开另一个终端,向管道当中输入内容可以实现写入内容
$ echo “string” > [管道名字]
此时读端也会显示内容
getcwd(NULL, 0);
#include //头文件
char *getcwd(char *buf, size_t size); //获取当前目录,相当于 pwd 命令
char *getwd(char *buf);
char *get_current_dir_name(void);
int chdir(const char *path); //修改当前目录,即切换目录,相当于 cd 命令
#include
int main()
{
chdir(“/tmp”);
printf(“current working directory: %s\n”,getcwd(NULL,0));
}
#include
#include
#include
int mkdir(const char *pathname, mode_t mode); //创建目录,mode 是目录权限
int rmdir(const char *pathname); //删除目录
查看环境变量
$ echo $PATH
修改环境(系统路径)变量(只对本次生效)
$ export PATH=$PATH:新目录
struct dirent{ino_t d_ino; //该文件的 inodeoff_t d_off; //到下一个 dirent 的偏移unsigned short d_reclen;//文件名长度unsigned char d_type; //所指的文件类型char d_name[256]; //文件名
};
#include
#include
DIR *opendir(const char *name); //打开一个目录
struct dirent *readdir(DIR *dir); //读取目录的一项信息,并返回该项信息的结构体指针
void rewinddir(DIR *dir); //重新定位到目录文件的头部
void seekdir(DIR *dir,off_t offset);//用来设置目录流目前的读取位置
off_t telldir(DIR *dir); //返回目录流当前的读取位置
int closedir(DIR *dir); //关闭目录文件
$ ls -ial
查看所有文件的 inode 信息
$ ln 当前文件 目标
建立名为“目标”的硬链接
//在写端写入时添加 sleep(10)
...
sleep(10);
write
...
//再次测试的时候发现读端会明显延迟
//1 号进程
#include "header.h"
#include int main(int argc, char *argv[])
{
ARGS_CHECK(argc,3);
int fdr = open(argv[1],O_RDONLY);//管道打开的时候,必须要先将读写端都打开之后才能继续
int fdw = open(argv[2],O_WRONLY);
printf("I am chat1\n");
char buf[128] = {0};
while(1)
{
memset(buf,0,sizeof(buf));//将buf缓冲区清0
read(STDIN_FILENO, buf, sizeof(buf));//将标准输入的字符串写入buf缓冲区
write(fdw, buf, strlen(buf)-1);//写入2号管道文件
memset(buf,0,sizeof(buf));
read(fdr, buf, sizeof(buf));//读取1号管道文件
printf("buf = %s\n", buf);
}
return 0;
}//2 号
#include "header.h"
#include
int main(int argc, char *argv[])
{
ARGS_CHECK(argc,3);
int fdw = open(argv[1],O_WRONLY);//管道打开的时候,必须要先将读写端都打开之后才能继续
int fdr = open(argv[2],O_RDONLY);
printf("I am chat2\n");
char buf[128] = {0};
while(1)
{
memset(buf,0,sizeof(buf));
read(fdr, buf, sizeof(buf));//读取1号管道文件
printf("buf = %s\n", buf);
memset(buf,0,sizeof(buf));
read(STDIN_FILENO, buf, sizeof(buf));//从标准输入读取字符串到buf缓冲区
write(fdw, buf, strlen(buf)-1);//写入2号管道文件
}
return 0;
}
//这里经常会有阻塞
#include
#include
int select(int maxfd, fd_set *readset,fd_set *writeset, fd_set *exceptionset, struct timeval
* timeout);
//返回值为0 代表超时,返回值为-1 代表出错
/*
select函数的参数解释:
maxfd:最大的文件描述符(其值应该为最大的文件描述符字 + 1)
readset:内核读操作的描述符字集合
writeset:内核写操作的描述符字集合
exceptionset:内核异常操作的描述符字集合
timeout:等待描述符就绪需要多少时间。NULL 代表永远等下去,一个固定值代表等待固定时间,0 代表根本不等待,检查描述字之后立即返回
*///readset、writeset、exceptionset 都是 fd_set 集合
//集合的相关操作如下:
void FD_ZERO(fd_set *fdset); /* 将所有 fd 清零 */
void FD_SET(int fd, fd_set *fdset); /* 增加一个 fd */
void FD_CLR(int fd, fd_set *fdset); /* 删除一个 fd */
int FD_ISSET(int fd, fd_set *fdset); /* 判断一个 fd 是否有设置 */
//chat1.c
//编译后运行
//$ ./chat1 1.pipe 2.pipe
#include
int main(int argc, char *argv[])
{ARGS_CHECK(argc,3);int fdr = open(argv[1],O_RDONLY);//管道打开的时候,必须要先将读写端都打开之后才能继续int fdw = open(argv[2],O_WRONLY);//对管道2只读打开printf("I am chat1\n");char buf[128] = {0};//设置缓冲区int ret;fd_set rdset;//rdset文件描述符while(1){FD_ZERO(&rdset);//清空fdset中的fdFD_SET(STDIN_FILENO,&rdset);//将标准输入添加到集合中FD_SET(fdr,&rdset);//将只读的管道文件1添加到集合中ret = select(fdr+1, &rdset, NULL, NULL, NULL);//设置最大文件操作符,将rdset设为读区的描述字集合if(FD_ISSET(STDIN_FILENO, &rdset)){ //判断rdset集合中是否设置了标准输入输出memset(buf,0,sizeof(buf)); //清空缓冲区read(STDIN_FILENO, buf, sizeof(buf)); //读取标准输入的内容到buf缓冲区中write(fdw, buf, strlen(buf)-1); //将buf缓冲区的内容写入管道文件2,即fdw打开的只写文件}if(FD_ISSET(fdr, &rdset)){ //判断rdset集合中是否设置了只读文件的描述字memset(buf,0,sizeof(buf)); //清空缓冲区read(fdr, buf, sizeof(buf)); //将fdr对应的只读文件内容读取到缓冲区bufprintf("buf = %s\n", buf); //打印buf内容}}return 0;
}//chat2.c
//编译后运行(注意管道建立连接的顺序)
//$ ./chat2 1.pipe 2.pipe
#include
int main(int argc, char *argv[])
{ARGS_CHECK(argc,3);int fdw = open(argv[1],O_WRONLY);//管道打开的时候,必须要先将读写端都打开之后才能继续int fdr = open(argv[2],O_RDONLY);printf("I am chat2\n");char buf[128] = {0};int ret;fd_set rdset;while(1){FD_ZERO(&rdset);FD_SET(STDIN_FILENO,&rdset);FD_SET(fdr,&rdset);ret = select(fdr+1, &rdset, NULL, NULL, NULL);if(FD_ISSET(STDIN_FILENO, &rdset)){memset(buf,0,sizeof(buf));read(STDIN_FILENO, buf, sizeof(buf));write(fdw, buf, strlen(buf)-1);}if(FD_ISSET(fdr, &rdset)){memset(buf,0,sizeof(buf));read(fdr, buf, sizeof(buf));printf("buf = %s\n", buf);}}return 0;
}
//fd_set 的成员是一个长整型的结构体
typedef long int __fd_mask;
//将字节转化为位
#define __NFDBITS (8 * (int) sizeof (__fd_mask))
//位图-判断是否存在文件描述符 d
#define __FD_MASK(d) ((__fd_mask) (1UL << ((d) % __NFDBITS)))
//select 和 pselect 的 fd_set 结构体
typedef struct
{//成员就是一个长整型的数组,用来实现位图__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
} fd_set;
// fd_set 里面文件描述符的数量,可以使用 ulimit -n 进行查看
#define FD_SETSIZE __FD_SETSIZE
//将写端的程序修改为如此
#include
int main(int argc, char *argv[])
{ARGS_CHECK(argc,2);int fdw = open(argv[1],O_WRONLY);ERROR_CHECK(fdw,-1,"open");printf("fdw = %d\n",fdw);close(fdw);//这里将写端直接关闭sleep(10);//然后睡眠 10sreturn 0;
}
if(FD_ISSET(STDIN_FILENO, &rdset)){ //判断集合中是否有标准输入memset(buf,0,sizeof(buf)); //清空缓冲区read_ret = read(STDIN_FILENO, buf, sizeof(buf)); //将标准输入的内容读取到buf缓冲区中if(read_ret == 0){ //判断read是否为0,如果为0就break退出循环printf("chat is broken!\n");break;}write(fdw, buf, strlen(buf)-1); //否则就将读入的内容写入缓冲区中
}if(FD_ISSET(fdr, &rdset)){memset(buf,0,sizeof(buf));read_ret = read(fdr, buf, sizeof(buf));if(read_ret == 0){printf("chat is broken!\n");break;} printf("buf = %s\n", buf);}
...
#使用 ctrl+c 终止程序会导致程序的返回值不为 0
#可以改用 ctrl+d 来终止 stdin(相当于输入了 EOF)
#$?代表了上个执行程序的返回值
$echo $?
struct timeval
{long tv_sec;//秒long tv_usec;//微秒
};//用法
...
struct timeval timeout; //定义一个timeval的变量
while(1){bzero(&timeout, sizeof(timeout));timeout.tv_sec = 3;//设置等待时间,如果是NULL则永远等待,如果是0则不等待ret = select(fdr+1, &rdset, NULL, NULL, &timeout);//传入参数if(ret > 0){...}else printf("time out!\n");
}
#include
int main(int argc, char* argv[])
{ARGS_CHECK(argc, 2); //用同一个管道进行测试iint fdr = open(argv[1],O_RDWR);int fdw = open(argv[1],O_RDWR);//可以一次性打开管道的读写端fd_set rdset,wrset; int ret;char buf[128];while(1){ FD_ZERO(&rdset); //清空写集合FD_ZERO(&wrset); //清空读集合FD_SET(fdr, &rdset);//将读操作放入读集合FD_SET(fdw, &wrset);//将写操作放入写集合ret = select(fdw+1, &rdset, &wrset, NULL, NULL);//设置select监听的读写集合位置if(FD_ISSET(fdr, &rdset)){ //如果返回值不为0,即没有超时可以读bzero(buf, sizeof(buf));//清空缓冲区bufread(fdr, buf, sizeof(buf));//读取管道内容到缓冲区中puts(buf);usleep(250000); //进程挂起0.25秒} if(FD_ISSET(fdw, &wrset)){ //如果返回值不为0,即可以写write(fdw,"helloworld", 10);usleep(500000);//写后进程挂起0.5秒,给读操作的时间} }
}