注意!站在内存的角度去考虑输入和输出。
输入:从文件到内存
输出:从内存到文件
标准C库是跨平台的,不同的操作系统会调用不同系统的API
缓冲区,默认8KB
磁盘读写,使用标准C库IO函数,提高系统效率;网络通信使用Linux系统IO,无缓冲。
man 3 fopen,查看文件函数具体明细
File是一个结构体,主要包含三大块内容,如图所示
虚拟地址空间不存在,解决程序加载内存出现的问题,以及解释程序中的堆栈模型等。进程与程序的区别:进程是运行的程序,加载到内存中;程序是磁盘上的代码。32位机器,虚拟地址2^32;64位机器,虚拟地址2的48次方。虚拟地址空间被MMU映射到物理内存。
使用系统调用,通过系统API对内核数据进行操作。
图上是32位的,4G
程序:是文件,不占用内存,只占用磁盘(test、test.c)
进程:把可执行程序启动起来,操作系统会为其分配资源,也就是进程。占用内存空间,具有虚拟地址空间
文件描述符:在进程内核区,内核其实就是一个程序,其中有一个结构体叫做PCB(Process Control Block),其中就管理着文件描述符表
文件描述符表,是一个数组,可存储n个文件描述符。每个文件描述符都可定位到一个文件。一个进程可同时打开多个文件。默认大小是1024。Linux系统,一切皆文件。一个文件可被打开多次,每次打开的文件描述符不一样,如第一次打开文件进行读操作,第二次打开文件进行写操作。系统调用close可以释放掉文件描述符,使用open获取文件描述符。
使用man指令查看函数手册,linux系统API都在第二章,第三章是标准C库的API
man 2 open
注:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
这里不是函数重载,因为是C语言,这里其实是可变参数
/*
#include
#include
#include //函数声明
//打开一个已经存在的文件
//flags定义为宏,放在前两个头文件中,方便在其他地方也能用
int open(const char *pathname, int flags);
1)参数:
1、pathname:要打开的文件路径
2、-flags:对文件的操作权限及其他设置
原文: The argument flags must include one of the
following access modes: O_RDONLY, O_WRONLY, or
O_RDWR. These request opening the file
read-only, write-only, or read/write,
respectively.(参数标志必须包括以下访问模式之
一:O_RDONLY、O_WRONLY或O_RDWR。这些请求分别以只读、
只读或读/写方式打开文件,三者互斥)
O_RDONLY, O_WRONLY, O_RDWR这三个设置是互斥的
2)返回值:返回一个新的文件描述符,如果调用失败,
返回-1(在man 2 open 进入说明文档后,可以使用
/return value来查看这个函数的返回值说明)
errno:属于Linux系统函数库,库里面的一个全局变量,
记录的是最近的错误号。但是最终还是想看具体错误信息
所以使用perror(C库函数),打印具体信息
#include
void perror(const char *s);
s参数:用户描述,比如hello,最终输出的内容是 hello:***(实际的错误报告)
作用:打印error对应的错误描述
//创建一个新的文件
int open(const char *pathname, int flags, mode_t mode);//并非重载,而是可变参数实现
*/
#include
#include
#include //linux open
#include //C perror
#include //linux close
int main(){
int fd=open("a.txt",O_RDONLY);
if(fd==-1){
perror("open");
}
//关闭
close(fd);
return 0;
}
/*
#include
#include
#include
int open(const char *pathname, int flags, mode_t mode);
参数:
-pathname:要创建的文件的路径
-flags:对文件的操作权限和其他的设置
-必选项:O_RDONLY, O_WRONLY, O_RDWR 这三者之间是互斥的
-可选项:O_CREAT,文件不存在就创建(还有很多,man 2 open就能看到,文件的追加写入等等)
-mode:八进制的数,表示用户创建出的新的文件的操作权限,比如:0777
但最终的权限是:mode & ~umask(0002)
(0002转换成2进制:000 000 000 010)
(取反:111 111 111 101 0775)
(0777转成2进制: 000 111 111 111)
(二者相与:000 111 111 101 也就是0775)
0777 rwxrwxrwx ->111 111 111
& 0775 ->111 111 101
--------------------------------
111 111 101
(其他组不能对文件进行写操作)->用户、本组、其他组
按位与:0与任何数都为0
umask的作用就是抹去某些权限!
可通过umask 0022将掩码设置成0022,但仅对此终端有效
*/
#include
#include
#include
#include
#include
int main(){
//创建一个新的文件
int fd=open("create.txt",O_RDWR|O_CREAT,0777);
//O_RDWR|O_CREAT
//按位或:flags是一个int类型的数据,占4个字节,32位
//flags 32个位,每一位就是一个标志位
if(fd==-1){
perror("open");
}
//关闭
close(fd);
return 0;
}
//O_RDWR|O_CREAT
//按位或:flags是一个int类型的数据,占4个字节,32位
//flags 32个位,每一位就是一个标志位
解决xftp打开文件中文名乱码:
https://jingyan.baidu.com/article/0bc808fc03296a1bd485b9ed.html
read:从文件中读取数据到内存中
write:把内存中的数据写到文件当中
结合read和write可实现文件的拷贝功能
/*
#include
ssize_t read(int fd, void *buf, size_t count);
参数:
- fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
- buf:需要读取数据存放的地方,数组的地址(传出参数)
- count:指定的数组的大小
返回值:
- 成功:
>0: 返回实际的读取到的字节数
=0:文件已经读取完了
- 失败:-1 ,并且设置errno
#include
ssize_t write(int fd, const void *buf, size_t count);
参数:
- fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
- buf:要往磁盘写入的数据,数组
- count:要写的数据的实际的大小
返回值:
成功:实际写入的字节数
失败:返回-1,并设置errno
*/
#include
#include
#include
#include
#include
int main() {
// 1.通过open打开english.txt文件
int srcfd = open("english.txt", O_RDONLY);
if(srcfd == -1) {
perror("open");
return -1;
}
// 2.创建一个新的文件(拷贝文件)
int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
//-rwxrwxrwx -rw_rw_r__ &(~umask)
//-rw_rw_r__ & (0775 就是去掉了最后一个w)
//最后还是-rw_rw_r__
if(destfd == -1) {
perror("open");
return -1;
}
// 3.频繁的读写操作
char buf[1024] = {0};
int len = 0;
while((len = read(srcfd, buf, sizeof(buf))) > 0) {
write(destfd, buf, len);
}
// 4.关闭文件
close(destfd);
close(srcfd);
return 0;
}
获取文件从不同位置开始偏移一定量后,文件指针的位置。
作用:
1、移动文件指针到文件头: lseek(fd,0,SEEK_SET);
2、获取当前文件指针的位置: lseek(fd,0,SEEK_CUR)
3、获取文件长度: lseek(fd,0,SEEK_END);
4、拓展文件的长度,当前文件10b,拓展成110b,增加了100个字节
lseek(fd,100,SEEK_END);
应用:下载文件时,先扩展文件,预留文件大小后再下载数据
注意:需写一次数据,扩展才生效
/*
#include //放置一些宏定义
#include //Unix标准头文件
off_t lseek(int fd, off_t offset, int whence);//Linux系统,对文件指针进行操作
参数:
-fd:文件描述符,通过open得到的,通过这个fd操作某个文件
-offset:偏移量
-whence:
SEEK_SET
设置从文件开始指针的偏移量
SEEK_CUR
设置偏移量:当前位置+第二个参数offset的值.
SEEK_END
设置偏移量:文件大小+第二个参数offset的值
返回值:返回文件指针的位置
作用:
1、移动文件指针到文件头
lseek(fd,0,SEEK_SET);
返回从文件开始偏移0个单位后,指针的位置
2、获取当前文件指针的位置
lseek(fd,0,SEEK_CUR)
返回从文件当前位置0个单位后,指针的位置
3、获取文件长度
lseek(fd,0,SEEK_END);
返回从文件末尾偏移0个单位后,指针的位置
4、拓展文件的长度,当前文件10b,拓展成110b,增加了100个字节
lseek(fd,100,SEEK_END);
注意:需写一次数据,扩展才生效
应用:下载文件时,先扩展文件,预留文件大小后再下载数据。(比如有个5G的数据要下载到电脑,就会先在电脑创建一个5G的文件,然后一点点填充空字符。否则如果电脑没有5G空间,就算前4.8G下载了也没有用)
//标准C库
#include
int fseek(FILE *stream, long offset, int whence);
*/
#include
#include
#include
#include
#include
int main(){
int fd=open("hello.txt",O_RDWR);
if(fd==-1){
perror("open");
return -1;
}
//扩展文件的长度
int ret=lseek(fd,100,SEEK_END);
if(ret==-1){
perror("lseek");
return -1;
}
//写入一个空数据
write(fd," ",1);
//关闭文件
close(fd);
return 0;
}
//最终其实是112
int stat(const char *pathname, struct stat *statbuf);作用:获取文件的相关信息
直接用stat命令获取文件信息
st_mode类型一共有16位,每一组权限占3位,一共4组。共计12位。
权限例如:
00004、00040,此时后四位与二进制的换算可以直接看成8进制的,第一个0看成4位二进制。
也就是 0000 000 000 000 100、0000 000 000 100 000
文件类型例如:
0120000 后四个0代表八进制,012此时是一个八进制整体 012不能直接转成000 001 010,而是0+8+2 = 10,转换成4位二进制是:1010,所以这一串的整体是:1010 000 000 000 000
Linux有七种文件类型。st_mode通过一个或多个标志位表示不同的文件类型。
判断文件权限&与操作:
比如与0001,如果有这个权限,最后一位的结果就是1,如果没有那就是0。并且除了这一位全都置零,方便比较
判断文件类型:
将st_mode与掩码与操作后,并与具体文件类型进行比较:
掩码:170000,其实就是1111 000 000 000 000
前四位是1或者0都会被保留下来,后面12位直接置零,方便跟具体类型比较
/*
#include
#include
#include
int stat(const char *pathname, struct stat *statbuf);
作用:获取文件的相关信息
参数:
-pathname:操作的文件路径
-statbuf:结构体变量,传出参数,用于保存获取到的信息
返回值:
0:成功
-1:失败,并且设置errno
int lstat(const char *pathname, struct stat *statbuf);//获取软链接文件的信息
*/
#include
#include
#include
#include
int main(){
struct stat statbuf;
int ret=stat("a.txt",&statbuf);
if(ret==-1){
perror("stat");
return -1;
}
printf("size:%ld\n",statbuf.st_size);
return 0;
}
关于lstat函数
作用:查看软连接的文件信息
使用ln -s 给a.txt创建软链接 b.txt
可以看到b的大小其实只有5但是如果用stat函数读b.txt得到的会是a的文件信息,直接vim b.txt其实打开的也是a文件
如果想知道b的文件信息,就要使用lstat函数
//模拟实现ls-l 指令
//-rw-rw-r-- 1 xiaohong xiaohong 13 5月 25 22:34 a.txt
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[]){
//判断输入的参数是否正确
if(argc<2){
printf("%s filename\n",argv[0]);
return -1;
}
//通过stat函数获取用户传入的文件信息
struct stat st;
int ret=stat(argv[1],&st);
if(ret==-1){
perror("stat");
return -1;
}
//获取文件类型和文件权限
char perms[11]={0};//用于保存文件类型和文件权限的字符串
switch(st.st_mode & S_IFMT){
case S_IFLNK:
perms[0]='l';
break;
case S_IFDIR:
perms[0]='d';
break;
case S_IFREG:
perms[0]='-';
break;
case S_IFBLK:
perms[0]='b';
break;
case S_IFCHR:
perms[0]='c';
break;
case S_IFSOCK:
perms[0]='s';
break;
case S_IFIFO:
perms[0]='p';
break;
default:
perms[0]='?';
break;
}
//判断文件的访问权限
//文件所有者
perms[1]=(st.st_mode&S_IRUSR)?'r':'-';
perms[2]=(st.st_mode&S_IWUSR)?'w':'-';
perms[3]=(st.st_mode&S_IXUSR)?'x':'-';
//文件所有者
perms[4]=(st.st_mode&S_IRGRP)?'r':'-';
perms[5]=(st.st_mode&S_IWGRP)?'w':'-';
perms[6]=(st.st_mode&S_IXGRP)?'x':'-';
//其他人
perms[7]=(st.st_mode&S_IROTH)?'r':'-';
perms[8]=(st.st_mode&S_IWOTH)?'w':'-';
perms[9]=(st.st_mode&S_IXOTH)?'x':'-';
//硬连接数
int linkNum=st.st_nlink;
//文件所有者
//st.st_uid只能返回所有者的id,但我们想知道用户名
//直接看结构体password的成员pw_name
char * fileUser=getpwuid(st.st_uid)->pw_name;
//extern struct passwd *getpwuid (__uid_t __uid);
// struct passwd
// {
// char *pw_name; /* Username. */
// char *pw_passwd; /* 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. */
// };
//文件所在组
char * fileGrp=getgrgid(st.st_gid)->gr_name;
//文件大小
long int fileSize=st.st_size;
//获取修改的时间
char * time=ctime(&st.st_mtime);
char mtime[512]={0};
strncpy(mtime,time,strlen(time)-1); //拷贝字符串 去掉time自带的回车换行
char buf[1024];
sprintf(buf,"%s %d %s %s %ld %s %s",perms,linkNum,fileUser,fileGrp,fileSize,mtime,argv[1]);
printf("%s\n",buf);
return 0;
}
/*
#include
int access(const char *pathname,int mode);
作用:判断某个文件是否有某个权限,或者判断文件是否存在
参数:
-pathname:判断的文件路径
-mode:
R_OK:判断是否有读权限
W_OK:判断是否有写权限
X_OK:判断是否有执行权限
F_OK:判断文件是否存在
返回值:成功返回0,失败返回-1
*/
#include
#include
int main(){
int ret=access("a.txt",F_OK);
if(ret==-1){
perror("acess");
return -1;
}
printf("文件存在!!!\n");
return 0;
}
2、chmod (changemod) 修改权限
/*
#include
int chmod(const char* pathname,mode_t mode);
作用:修改文件的权限
参数:
-pathname:需要修改的文件的路径
-mode:需要修改的权限值,八进制的数
返回值:成功返回0,失败返回-1
*/
#include
#include
int main(){
int ret=chmod("a.txt",0777);
if(ret==-1){
perror("chmod");
return -1;
}
return 0;
}
3、chown (change owner)改变文件所有者或者所在组
int chown(const char* path,uid_t owner,gid_t group);
1、vim /etc/passwd 显示所有用户的id和所在组id
依次是:用户名、用户id、所在组id
2、vim /etc/group 显示所有组的名称和对应的id
root组的id就是0
3、使用sudo创建新用户和分组
uid = userid
gid = groupid
4、truncate 缩减或者扩展文件的大小
/*
#include
#include
int truncate(const char* pathname,off_t length);
作用:缩减或者扩展文件的尺寸至指定的大小
参数:
-pathname:需要修改的文件路径
-length:需要最终文件变成的大小
返回值:
成功返回0,失败返回-1
*/
#include
#include
#include
int main(){
int ret=truncate("b.txt",5);
if(ret==-1){
perror("truncate");
return -1;
}
return 0;
}
1、mkdir 创建目录
在创建的时候要注意:想要进入目录,就必须要给可执行权限
/*
#include
#include
int mkdir(const char* pathname,mode_t mode);
作用:创建一个目录
参数:
pathname:创建的目录路径
mode:权限,八进制的数,与掩码进行与操作获得最后的权限
最终是:mode & ~umask & 0777
返回值:
成功返回0,失败返回-1
*/
#include
#include
#include
int main(){
int ret=mkdir("aaa",0777);
if(ret==-1){
perror("mkdir");
return -1;
}
return 0;
}
2、rmdir
只能删除空目录(目录下不能有东西)
int rmdir(const char* pathname);
3、rename 修改目录名称
/*
#include
int rename(const char *oldpath, const char *newpath);
*/
#include
int main() {
int ret = rename("aaa", "bbb");
if(ret == -1) {
perror("rename");
return -1;
}
return 0;
}
4、chdir 修改进程的工作目录
5、getcwd 获取当前工作目录
/*
#include
int chdir(const char* path);
作用:修改进程的工作目录
比如在/home/nowcoder启动了一个可执行程序a.out,进程的工作目录就是/home/nowcoder
参数:
path:需要修改的工作目录
返回值:
成功返回0,失败返回-1
#include
char * getcwd(char *buf,size_t size);
作用:获取当前工作目录
参数:
-buf:存储的路径,指向的是一个数组(传出参数)
-size:数组的大小
返回值:
返回的指向的一块内存,这个数据就是第一个参数
*/
#include
#include
#include
#include
#include
int main(){
//获取当前的工作目录
char buf[128];
getcwd(buf,sizeof(buf));
printf("当前的工作目录是:%s\n",buf);
//修改工作目录
int ret=chdir("/home/xiaohong/Linux/lesson13");
if(ret==-1){
perror("chdir");
return -1;
}
//创建一个新的文件
int fd=open("chdir.txt",O_CREAT|O_RDWR,0664);
if(fd==-1){
perror("open");
return -1;
}
close(fd);
//获取当前的工作目录
char buf1[128];
getcwd(buf1,sizeof(buf1));
printf("当前的工作目录是:%s\n",buf1);
return 0;
}
/*
//打开一个目录
#include
#include
DIR* opendir(const char *name);
参数:
-name:需要打开的目录名称
返回值:
DIR* 类型,理解为目录流
错误返回NULL
//读取目录中的数据
#include
struct dirent *readdir(DIR *dirp);
-参数:dirp是opendir返回的结果
-返回值:
struct dirent,代表读取到的文件的信息
读取到了末尾或者失败了,返回NULL
//关闭目录
#include
#include
int closedir(DIR* drip);
*/
#include
#include
#include
#include
#include
int getFileNum(const char* path);
//读取目录下所有的普通文件的个数
int main(int argc,char* argv[]){
if(argc<2){
printf("%s path\n",argv[0]);
return -1;
}
int num=getFileNum(argv[1]);
printf("普通文件的个数为:%d\n",num);
return 0;
}
//用于获取目录下所有普通文件的个数
int getFileNum(const char* path){
//1、打开目录
DIR * dir=opendir(path);
if(dir==NULL){
perror("opendir");
exit(0);
}
struct dirent *ptr;
//记录普通文件的个数
int total=0;
while((ptr=readdir(dir))!=NULL){
//获取名称
char *dname=ptr->d_name;
//忽略掉.和..
if(strcmp(dname,".")==0||strcmp(dname,"..")==0){
continue;
}
//判断是否有普通文件还是目录
if(ptr->d_type==DT_DIR){
//目录,需要继续读取这个目录
char newpath[256];
sprintf(newpath,"%s/%s",path,dname);
total+=getFileNum(newpath);
}
if(ptr->d_type==DT_REG){
//普通文件
++total;
}
}
//关闭目录
closedir(dir);
return total;
}
/*
#include
int dup(int oldfd);
作用:复制一个新的文件描述符
fd=3, int fd1 = dup(fd),
fd指向的是a.txt, fd1也是指向a.txt
从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符
*/
#include
#include
#include
#include
#include
#include
int main() {
int fd = open("a.txt", O_RDWR | O_CREAT, 0664);
int fd1 = dup(fd);
if(fd1 == -1) {
perror("dup");
return -1;
}
printf("fd : %d , fd1 : %d\n", fd, fd1);
close(fd);
char * str = "hello,world";
int ret = write(fd1, str, strlen(str));
if(ret == -1) {
perror("write");
return -1;
}
close(fd1);
return 0;
}
/*
#include
int dup2(int oldfd,int newfd);
作用:重定向文件描述符
oldfd指向a.txt,newfd指向b.txt
调用函数成功后:newfd和b.txt做close,newfd指向了a.txt
oldfd必须是一个有效的文件描述符
若oldfd和newfd值相同,相当于什么都没有做
*/
#include
#include
#include
#include
#include
#include
int main(){
int fd=open("1.txt",O_RDWR|O_CREAT,0664);
if(fd==-1){
perror("open");
return -1;
}
int fd1=open("2.txt",O_RDWR|O_CREAT,0664);
if(fd1==-1){
perror("open");
return -1;
}
printf("fd:%d,fd1:%d\n",fd,fd1);
int fd2=dup2(fd,fd1);//fd2与fd1相同
if(fd2==-1){
perror("dup2");
return -1;
}
//通过fd1去写数据,实际操作的是1.txt,而不是2.txt
char* str="hello,dup2";
int len=write(fd1,str,strlen(str));
if(len==-1){
perror("write");
return -1;
}
printf("fd:%d,fd1:%d,fd2:%d\n",fd,fd1,fd2);
close(fd);
close(fd1);
return 0;
}
/*
#include
#include
int fcntl(int fd, int cmd, ...);
参数:
fd : 表示需要操作的文件描述符
cmd: 表示对文件描述符进行如何操作
- F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
int ret = fcntl(fd, F_DUPFD);
- F_GETFL : 获取指定的文件描述符文件状态flag
获取的flag和我们通过open函数传递的flag是一个东西。
- F_SETFL : 设置文件描述符文件状态flag
必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
可选性:O_APPEND, O)NONBLOCK
O_APPEND 表示追加数据
NONBLOK 设置成非阻塞
...:可变参数
阻塞和非阻塞:描述的是函数调用的行为。阻塞,返回值返回前当前进程被挂起直到返回。非阻塞,调用函数,立即返回,不会阻塞进程或线程。
*/
#include
#include
#include
#include
int main() {
// 1.复制文件描述符
// int fd = open("1.txt", O_RDONLY);
// int ret = fcntl(fd, F_DUPFD);
// 2.修改或者获取文件状态flag
int fd = open("1.txt", O_RDWR);
if(fd == -1) {
perror("open");
return -1;
}
// 获取文件描述符状态flag
int flag = fcntl(fd, F_GETFL);
if(flag == -1) {
perror("fcntl");
return -1;
}
flag |= O_APPEND; // flag = flag | O_APPEND
// 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
int ret = fcntl(fd, F_SETFL, flag);
if(ret == -1) {
perror("fcntl");
return -1;
}
char * str = "nihao";
write(fd, str, strlen(str));
close(fd);
return 0;
}