接下来学习的都是遵循Linux系统调用规范的POSIX系统调用
后面的const char *mode
中,在Linux中”r“
和”rb“
一样。
分配在用户态空间。
打开的另一种模式:
a+
:使用a+
时,读从头开始读,写从尾部开始写,若文件不存在,则创建文件。
func.h
的文件路径在cd /usr/include
#include
int main(int argc,char *argv[])
{
//./fopen file1
ARGS_CHECK(argc,2);
FILE *fp;
fp = fopen(argv[1],"a+");
ERROR_CHECK(fp,NULL,"fopen");
char buf[7] = {0};
fread(buf,1,sizeof(buf),fp);
puts(buf);
fwrite("how are you",1,11,fp);
fclose(fp);
return 0;
}
a+
模式:
就算使用fseek(fp,0,SEEK_SET)
,即使移动ptr
写入依然从尾开始。
#include
int main(int argc,char *argv[])
{
//.chmod 777 file1
ARGS_CHECK(argc,3);
mode_t mode;
sscanf(argv[1],"%o",&mode);
//把传进来的字符串变成八进制整数
int ret = chmod(argv[2],mode);
ERROR_CHECK(ret,-1,"chmod");
return 0;
}
eg : char* getcwd(char *buf,size_t size);
char*
作为返回值的指针,所指向的空间也被已经分配。
#include
int main(int argc,char *argv[])
{
char buf[128] = {0};
char *ret = getcwd(buf,sizeof(buf));
ERROR_CHECK(ret,NULL,"getcwd");
puts(buf);
puts(ret);
//getcwd可以自动申请空间,不用想上面那样手动分配空间
printf("%s\n",getcwd(NULL,0));
return 0;
}
#include
int main(int argc,char* argv[])
{
// ./chdir dit
ARGS_CHECK(argc,2);
printf("before chdir = %s\n",getcwd(NULL,0));
int ret = chdir(argv[1]);
ERROR_CHECK(ret,-1,"chdir");
printf("after chdir = %s\n",getcwd(NULL,0));
return 0;
}
#include
int main(int argc,char *argv[])
{
ARGS_CHECK(argc,2);
int ret = mkdir(argv[1],0777);
// 0 表示八进制字面值常量
// 依然会受到umask的影响
ERROR_CHECK(ret,-1,"mkdir");
return 0;
}
#include
int main(int argc,char *argv[])
{
ARGS_CHECK(argc,2);
int ret = rmdir(argv[1]);
//只能删除空目录
ERROR_CHECK(ret,-1,"rmdir");
return 0;
}
由链表存储,目录流在每次读取的时候都会移动ptr
。
其中DIR *
指目录流
其中,struct dirent *readdir
指目录项,要把目录项读出来
这里面,d_ino
:磁盘地址;d_off
:next指针;d_reclen
:本结构体长度;d_type
:类型;d_name[256]
:名字。
#include
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,2);
DIR *dirp = opendir(argv[1]);
ERROR_CHECK(dirp,NULL,"opendir");
struct dirent *pdirent;
while((pdirent = readdir(dirp)) != NULL)
{
printf("ino = %ld,reclen = %d,type = %d,name = %s\n",
pdirent ->d_ino,
pdirent ->d_reclen,
pdirent ->d_type,
pdirent ->d_name);
}
closedir(dirp);
return 0;
}
#include
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,2);
DIR *dirp = opendir(argv[1]);
ERROR_CHECK(dirp,NULL,"opendir");
struct dirent *pdirent;
long loc;
while((pdirent = readdir(dirp)) != NULL)
{
printf("ino = %ld,reclen = %d,type = %d,name = %s\n",
pdirent ->d_ino,
pdirent ->d_reclen,
pdirent ->d_type,
pdirent ->d_name);
}
if(strcmp(pdirent->d_name,"dir1") == 0)
{
loc = telldir(dirp);
}
puts("----------------------------------");
seekdir(dirp,loc);
pdirent = readdir(dirp);
if(pdirent != NULL)
{
printf("ino = %ld,reclen = %d,type = %d,name = %s\n",
pdirent ->d_ino,
pdirent ->d_reclen,
pdirent ->d_type,
pdirent ->d_name);
}
closedir(dirp);
return 0;
}
显示文件的具体信息
struct stat *statbuf
指向的空间一定是分配好的
在man配置中,struct stat结构体如下:
struct stat {
dev_t st_dev; /* 文件设备编号 */
ino_t st_ino; /* inode节点 */
mode_t st_mode; /* 文件的类型和存储的权限 */
nlink_t st_nlink; /* 连到该文件的硬链接数,刚建立的文件值为1 */
uid_t st_uid; /* 用户id */
gid_t st_gid; /* 组id */
dev_t st_rdev; /* (设备类型)若此文件为设备文件,则为设备编号 */
off_t st_size; /* 文件大小 */
blksize_t st_blksize; /* 块大小(文件系统的I/O缓冲区大小) */
blkcnt_t st_blocks; /* 块数 */
/* 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; /* 最后一次访问时间 */
struct timespec st_mtim; /* 最后一次修改时间(指文件内容) */
struct timespec st_ctim; /* 最后一次属性改变的时间 */
#define st_atime st_atim.tv_sec
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
其中重要的变量,st_mode
:权限和类型;st_size
:文件大小
用stat
可以实现ls -al
#include
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,2);
DIR *dirp = opendir(argv[1]);
ERROR_CHECK(dirp,NULL,"opendir");
chdir(argv[1]);
//为了文件名直接可以当路径用,一个bug
struct dirent *pdirent ;
struct stat statbuf ;
while((pdirent = readdir(dirp)) != NULL)
{
int ret = stat(pdirent->d_name,&statbuf);
ERROR_CHECK(ret,-1,"stat");
printf("%6o %ld %s %s %6ld %15s %s",
statbuf.st_mode,
statbuf.st_nlink,
getpwuid(statbuf.st_uid)->pw_name,
getgrgid(statbuf.st_gid)->gr_name,
statbuf.st_size,
pdirent->d_name,
ctime(&(statbuf.st_mtime)));
}
closedir(dirp);
return 0;
}
实现类似于tree命令,深度优先遍历、递归
作用:printf函数调用的主要用途就是把一个字符串放在一个已知的字符数组里去。其实这是一个很常用的库函数,在解决某些OJ题的时候会经常用到它来帮助实现字符串的转移和储存。
第一个参数:这个参数就是接收字符串的字符数组。
第二个参数:这个参数就是要传的字符串了。
#include
int DFSprint(char *path,int width)
{
DIR *dirp = opendir(path);
ERROR_CHECK(dirp,NULL,"opendir");
struct dirent *pdirent ;
char newPath[1024] = {0};
while((pdirent = readdir(dirp)) != NULL)
{
if(strcmp(pdirent->d_name,".") == 0
||strcmp(pdirent->d_name,"..") == 0)
{
continue;
}
printf("%*s%s\n",width,"",pdirent->d_name);
if(pdirent->d_type == DT_DIR)
//这是一个目录
{
sprintf(newPath,"%s%s%s",path,"/",pdirent->d_name);
DFSprint(newPath,width+4);
}
}
closedir(dirp);
return 0;
}
int main(int argc,char *argv[])
{
ARGS_CHECK(argc,2);
printf("%s\n",argv[1]);
DFSprint(argv[1],4);
return 0;
}
open函数是为了取代create函数而专门设立的,open若能成功运行,则会返回一个文件描述符
其中,O_RDWR|O_CREAT
要同时生效,再加上O_TRUNC
表示创建一个文件。
#include
int main(int argc,char *argv[])
{
ARGS_CHECK(argc,2);
int fd;
fd = open(argv[1],O_RDWR|O_CREAT|O_TRUNC);
ERROR_CHECK(fd,-1,"open");
close(fd);
return 0;
}
:%!xxd -r <==========> :%!xxd
#include
int main(int argc,char *argv[])
{
//./mycp src dest
ARGS_CHECK(argc,3);
int fdr = open(argv[1],O_RDONLY);//读的文件描述符
ERROR_CHECK(fdr,-1,"fdr");
int fdw = open(argv[2],O_RDWR|O_CREAT|O_EXCL);
ERROR_CHECK(fdw,-1,"fdw");
char buf[4096] = {0};
while(1)
{
memset(buf,0,sizeof(buf));
int ret = read(fdr,buf,sizeof(buf));
if(ret == 0)
{
break;
}
ERROR_CHECK(ret,-1,"read");
write(fdw,buf,ret);
}
close(fdr);
close(fdw);
return 0;
}
带缓冲的IO:fread / fwrite
文件大小大于磁盘分配的空间
建立了磁盘到内存之间的映射
步骤:
open
mmap
munmap
参数分析:
void *addr
:NULL,自动分配
size_t length
:映射区长度要写内存页的整数倍(4096的整数倍)
int prot
:
int flags
:
off_t offset
:0
(用法跟malloc相似)
参数分析:
void *addr
:mmap的返回值
size_t length
:每次回收的长度
#include
int main(int argc,char *argv[])
{
ARGS_CHECK(argc,2);
int fd = open(argv[1],O_RDWR);//打开文件
ERROR_CHECK(fd,-1,"open");
int ret = ftruncate(fd,5);//固定文件大小
ERROR_CHECK(ret,-1,"ftruncate");
char *p = (char*)mmap(NULL,5,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
ERROR_CHECK(p,MAP_FAILED,"mmap");
//建立文件和内存的映射,映射区在堆空间
p[5] = '\0';
//p[0] ~ p[4]为映射区
p[0] = 'H';
//修改映射区相当于修改文件
munmap(p,5);
close(fd);
return 0;
}
注:使用mmap直接操作数组就可以修改文件,不用像read、write那样修改文件。
#include
int main(int argc,char *argv[])
{
ARGS_CHECK(argc,2);
int fd = open(argv[1],O_RDWR);
ERROR_CHECK(fd,-1,"open");
lseek(fd,40960,SEEK_SET);
write(fd,"1",1);
close(fd);
return 0;
}
输出结果:
文件描述符是一个非负的索引值(一般从3开始,0、1、2已经被使用)。
文件流中有一个域,是文件描述符。
打开文件流时,间接使用了文件描述符。
不同数值的文件描述符指向同一个FO(内核块)。
复制文件描述符
第一个函数int dup(int oldfd)
:找到一个最小可用的fd,使其也指向这个文件对象。
第二个函数int dup2(int oldfd,int newfd)
:手动指定文件描述符进行复制,其实就是close(oldfd)
,让newfd
指向oldfd
原来指向的文件
#include
int main(int argc,char *argv[])
{
ARGS_CHECK(argc,2);
int fd = open(argv[1],O_RDWR);
ERROR_CHECK(fd,-1,"open");
printf("fd = %d\n",fd);
int fd1 = dup(fd);
ERROR_CHECK(fd1,-1,"dup");
printf("fd1 = %d\n",fd1);
write(fd,"hello",5);
write(fd1,"world",5);
close(fd);
close(fd1);
return 0;
}
文件对象是引用计数管理。
其实printf() <=========> write(1,......);
Q:关闭1号文件描述符,再让文件描述符联系文件,打印三句话,第一句话在屏幕上,第二句话在文件内,第三句话在屏幕上?
思路:
dup2(1,5);
dup2(3,1);->自动close(1)
dup2(5,1);
#include
int main(int argc,char *argv[])
{
ARGS_CHECK(argc,2);
int fd1 = open(argv[1],O_RDWR);
ERROR_CHECK(fd1,-1,"open");
printf("fd1 = %d\n",fd1);
dup2(STDOUT_FILENO,5);
//关闭STDOUT_FILENO,让5号文件描述符指向屏幕
dup2(fd1,STDOUT_FILENO);
printf("you cannot see me!\n");
dup2(5,STDOUT_FILENO);
printf("you can see me!\n");
close(fd1);
close(5);
return 0;
}
named pipe / FIFO
是用于进程间通信的一种文件,在文件系统中的一种映射,方便使用open函数。
创建管道,只用于通信,不存储数据
并且在建立好管道读写端之前,进程是阻塞的。
open函数
中,读端用O_RDONLY
,写端用O_WRONLY
全双工通信时,建立读写端要有顺序要求,否则会造成死锁。
读端:
int fdr = open(argv[1],O_RDONLY);
int fdw = open(argv[2],O_WRONLY);
写端:
int fdr = open(argv[2],O_RDONLY);
int fdw = open(argv[1],O_WRONLY);
chat1
#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);
puts("chat1\n");
char buf[128] = {0};
while(1)
{
memset(buf,0,sizeof(buf));
read(fdr,buf,sizeof(buf));
puts(buf);
read(STDIN_FILENO,buf,sizeof(buf));
write(fdw,buf,strlen(buf));
}
close(fdw);
close(fdr);
return 0;
}
chat2
#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);
puts("chat2\n");
char buf[128] = {0};
while(1)
{
read(STDIN_FILENO,buf,sizeof(buf));
write(fdw,buf,strlen(buf));
memset(buf,0,sizeof(buf));
read(fdr,buf,sizeof(buf));
puts(buf);
}
close(fdw);
close(fdr);
return 0;
}
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout)
其中:
int nfds
:指监听的文件描述符,一般是最大值+1。
第二、三、四参数:读、写、错误事件
struct timeval *timeout
:为超时时间,其值为超时剩余时间,一般是NULL,无限等待。
FD_ZERO(fd_set* fdset)
: 将fd_set变量的所有位初始化为0。
FD_SET(int fd, fd_set* fdset)
:在参数fd_set指向的变量中注册文件描述符fd的信息。
FD_CLR(int fd, fd_set* fdset)
:参数fd_set指向的变量中清除文件描述符fd的信息。
FD_ISSET(int fd, fd_set* fdset)
:若参数fd_set指向的变量中包含文件描述符fd的信息,则返回真
等待一个集合,集合监听了若干FD,只要集合中任意一个FD就绪,就解除阻塞。
创建集合 fd_set
集合初始化 FD_ZERO(&set)
注册待监听的FD在集合中 FD_SET
(以上三步是创建集合)
调用select
函数,进程阻塞,任意FD就绪,就接触阻塞
遍历所有FD,检查是否就绪 FD_ISSET
读取就绪FD中的数据
SIGPIPE
信号chat1
#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);
puts("chat1\n");
char buf[128] = {0};
fd_set rdset;
while(1)
{
FD_ZERO(&rdset);
FD_SET(fdr,&rdset);
FD_SET(STDIN_FILENO,&rdset);
select(fdr+1,&rdset,NULL,NULL,NULL);
if(FD_ISSET(fdr,&rdset))
{
printf("msg from pipe\n");
memset(buf,0,sizeof(buf));
int ret = read(fdr,buf,sizeof(buf));
if(ret == 0)
{
printf("chat is end\n");
break;
}
puts(buf);
}
if(FD_ISSET(STDIN_FILENO,&rdset))
{
printf("msg from stdin\n");
memset(buf,0,sizeof(buf));
int ret = read(STDIN_FILENO,buf,sizeof(buf));
if(ret == 0)
{
puts("I quit!\n");
write(fdw,"byebye",6);
break;
}
write(fdw,buf,strlen(buf));
}
}
close(fdw);
close(fdr);
return 0;
}
chat2
#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);
puts("chat1\n");
char buf[128] = {0};
fd_set rdset;
while(1)
{
FD_ZERO(&rdset);
FD_SET(fdr,&rdset);
FD_SET(STDIN_FILENO,&rdset);
select(fdr+1,&rdset,NULL,NULL,NULL);
if(FD_ISSET(fdr,&rdset))
{
printf("msg from pipe\n");
memset(buf,0,sizeof(buf));
int ret = read(fdr,buf,sizeof(buf));
if(ret == 0)
{
printf("chat is end\n");
break;
}
puts(buf);
}
if(FD_ISSET(STDIN_FILENO,&rdset))
{
printf("msg from stdin\n");
memset(buf,0,sizeof(buf));
int ret = read(STDIN_FILENO,buf,sizeof(buf));
if(ret == 0)
{
puts("I quit!\n");
write(fdw,"byebye",6);
break;
}
write(fdw,buf,strlen(buf));
}
}
close(fdw);
close(fdr);
return 0;
}
struct timeval有两个成员,一个是秒,一个是微秒, 所以最高精确度是微秒。
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 500000;
//精确度
FD_ZERO(&rdset);
FD_SET(fdr,&rdset);
FD_SET(STDIN_FILENO,&rdset);
int tret = select(fdr+1,&rdset,NULL,NULL,&timeout);
if(tret == 0)
{
printf("time out!\n");
}
当管道数据区满了,写入就会发生阻塞
#include
int main(int argc,char *argv[])
{
ARGS_CHECK(argc,2);
int fdr = open(argv[1],O_RDWR);
int fdw = open(argv[1],O_RDWR);
//非标准写法,用O_RDWR
ERROR_CHECK(fdr,-1,"open");
ERROR_CHECK(fdw,-1,"open");
char buf[128] = {0};
int cnt = 0;
fd_set rdset;
fd_set wrset;
while(1)
{
FD_ZERO(&rdset);
FD_ZERO(&wrset);
FD_SET(fdr,&rdset);
FD_SET(fdw,&wrset);
select(fdw+1,&rdset,&wrset,NULL,NULL);
if(FD_ISSET(fdr,&rdset))
{
printf("cnt = %d, read\n",cnt);
++cnt;
read(fdr,buf,2048);
sleep(1);
}
if(FD_ISSET(fdw,&wrset))
{
printf("cnt = %d, write\n",cnt);
++cnt;
write(fdw,buf,4097);
//write函数如果大于4096,进程最终会阻塞
}
}
}
使用select
时,会将fd_set
拷贝到内核态空间,select的底层原理其实就是poll。
轮询polling:
O(n)
对比epoll
: