Linux基础和系统编程 中
read和write实现cp
1.read成功返回读到的字节数,失败返回-1,并设置相应的errno 的值
2.参数
fd:文件描述符
buf:存数据的缓冲区
count:缓冲区的大小
3.write
参数:
fd:文件描述符
buf:待写出数据的缓冲区
count:数据大小
返回值:返回写入的字节数
0表示没有什么可写的
失败返回-1,设置errno
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<fcntl.h> #include<pthread.h> int main(int argc,char *argv[]) { • char buf[1024]; • int fd1=open(argv[1],O_RDONLY); if(fd1==-1) { perror("open argv1 error"); exit(1) } • int fd2=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0664); if(fd2==-1) { perror("open argv2 error"); exit(1) } • while((n=read(fd1,buf,1024))!=0) • { if(n<0) { perror("read error"); exit(1); } • write(fd2,buf,n); • } • close(fd1); • close(fd2); • return 0; }
4.void perror(const char*s)
perror("open error");
系统调用和库函数比较
1.write是系统调用函数,putc是库函数
2.fgetc比read,write快一点
3.内核有系统级缓冲,默认大小有4096,即4kb大小,会一次性往磁盘写4kb
4.从用户区到内核比较耗时,比磁盘访问时间短,从用户区到内核是电子的操作,从内核到磁盘是物理操作
5.read,write函数会在用户与内核交换时浪费时间
6.fputc函数有一个缓冲,在这个空间缓冲了4kb数据才会进入内核,加快了4096倍速度
7.read,write被称为无缓冲IO(Unbuffered I/O),但不保证不使用内核缓冲
8.strace可以查看一个命令的系统调用,例如:strace ./read_cmp_getc
9.预读入,缓输出
10.系统函数不比库函数高级,系统函数效率不一定比库函数高
一般优先使用库函数
11.系统调用和库函数各有各的使用场景
文件描述符
1.PCB,进程控制块(进程描述符)process control block
本质是一个结构体,其中有一个指针指向是文件描述符表
2.文件描述符是本质可以理解为指针(实际上不是指针,是键值对映射),
3.指针指向
struct file
{
文件信息
}
4.访问打开的文件,只需要有文件描述符
5.0对应STDIN_FILENO,标准输入
1对应STDOUT_FILENO,标准输出
2对应STDERR_FILENO,标准错误
一个进程最多打开1024个文件,最大下标是1023,修改需要改内核
6.新打开文件一定是可用文件描述符中最小的那个
7.FILE结构体包括:文件的偏移量,文件的访问权限,文件的打开标志,文件内核缓冲区的首地址
阻塞与非阻塞
1.读常规文件不会阻塞,不管有多少字节
当读设备文件和读网络文件时会阻塞
2./dev/tty对应终端文件
3.阻塞是文件的属性
4.改变终端阻塞的属性要加O_NOBLOCK
5.fd=-1:并且erron=EAGAIN或EWOULDBLOCK,说明不是read失败,而是read在读一个设备文件(网络文件),并且文件无数据
6.解决非阻塞需要设置超时
fcntl的属性
1.改变一个文件的访问控制属性
2.一个参数是文件描述符fd,一个参数是命令cmd,命令的类型个数决定了后续的参数个数
3.获取文件状态 F_GETFL
设置文件状态 F_SETFL
4.位图
以二进制位画图
5.int flags=fctl(0,F_GETFL)
flgs|=O_NONBLOCK
fcntl(0,F_SETFL,flgs)
lseek函数
1.fd文件描述符
offset偏移量
whence起始偏移位置:SEEK_SET/SEET_CUR/SEEK_END
2.off_t seek(int fd,off_t offset,int whence)
3.返回值是
成功:较起始位置偏移量
失败:-1 errno
4.文件的读写是同一个偏移位置
5.
#include<stdio.h> #include<unistd.h> #include<string.h> #include<fcntl.h> int main(){ • int fd,n; • char msg[]="It's a test for lseek\n"; • char ch; • fd=open("lseek.txt",O_RDWR|O_CREAT,0644); • if(fd<0){ • perror("open lseek.txt error"); • exit(1) } write(fd,msg,strlen(msg)); lseek(fd,0,SEEK_SET); while(n=read(fd,&ch,1)){ • if(n<0){ • perror("read error"); • exit(1); return 0; } } }
6.应用场景: 文件的读写使用同一偏移位置
使用lseek获取,拓展文件大小(要想使文件大小真正拓展,必须引起IO操作)
7.
获取文件大小:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<fcntl.h> #include<pthread.h> int main(int argc,char *argv[]) { • open(argv[1],O_RDWR); • if(fd==-1) • { • perror("open error"); • exit(1); • } int length=lseek(fd,0,SEEK_END); • printf("file size:%d\n",length); close(fd); return 0; }
拓展文件大小:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<fcntl.h> #include<pthread.h> int main(int argc,char *argv[]) { open(argv[1],O_RDWR); if(fd==-1) { perror("open error"); exit(1); } int length=lseek(fd,111,SEEK_END); printf("file size:%d\n",length); write(fd,"a",1); close(fd); return 0; }
8.^@称为文件空洞行
9.od -tcx filename 查看文件的16进制表示形式
od -tcd filename 查看文件的10进制表示形式
10.turncate拓展文件
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<fcntl.h> #include<pthread.h> int main(int argc,char *argv[]) { • int ret=truncate("dict.cp",250); • printf("ret=%d\n",ret); • return 0; }
传入传出参数
传入参数:
1.指针作为函数参数
2.通常有const关键字修饰
3.指针指向有效区域,在函数内部做读操作
传出参数:
1.指针作为函数参数
2.在函数调入之前,指针指向的空间可以无意义,但必须有效
3.在函数内部,做写操作
4.函数调用结束后,充当函数返回值
传入传出参数:
1.指针作为函数参数
2.在函数调用之前,指针指向的空间有实际意义
3.在函数内部,先做读操作,后做写操作
4.函数调用结束后,充当函数的返回值
目录项和inode
inode
本质是一个结构体,存储文件的属性信息,权限,类型,大小,时间,用户,盘块位置
dentry(目录项)
存储文件名,inode
文件由inode和dentry组成
硬链接只有dentry不同
磁盘空间没有擦除,只有覆盖
stat函数
1.获取文件属性(从inode结构体中获取)
2.int stat(const char *path,struct stat *buf)
参数:
path:文件路径
buf:传出参数:存放文件属性
返回值:成功:0
失败:-1 errno
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<sys/stat.h> #include<fcntl.h> #include<pthread.h> int main(int argc,char *argv[]) { struct stat sbuf • int ret=stat(argv[1],&sbuf) • if(ret==-1) { • perror("stat error"); • exit(1); } printf("file size:%d\n",sbuf.st_size); return 0; }
3.宏函数返回值是真假
Istat和stat
1.
需要头文件#include<sys/stat.h>
if(S_ISREG(sb.st_mode))
{
printf("It's a regular\n");
}
2.mkfifo
创建管道
3.stat穿透,默认可以穿透符号链接
不想穿透要换函数,lstat不会穿透
4.cat可以穿透符号链接,vim不可以
5.获取文件大小:buf.st_size
获取文件类型:buf.st_mode
6.文件权限是16位(2字节)后九个是rwxrwxrwx,对应u,g,o
5~7是特殊权限位
最前面四位是文件类型
前4位是掩码
获取前四位信息
switch(sb.st_mode&S_IFMT)
{
• case S_IFBLK: printf("block device\n")
}
传出参数与返回值
1.传出参数有返回值的功能,但是没有替代返回值
2.传出参数也可以向函数返回值,传出参数可以充当函数返回值
link和unlink隐式回收
1.link是实现硬链接的函数
2.LInux下删除文件的机制:不断将st nlink-1,直到减到0为止,没有目录对应的文件,将会被操作系统择机释放
3.我们删除文件,从某种意义上说,只是让文件具备了被释放的条件
4.unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放掉,要等所有打开文件的进程关闭该文件,才会择机释放
文件目录rwx权限差异
1.readlink读符号链接
2.getcwd 获取进程当前工作目录
3.vi打开目录,目录的内容叫目录项
4.文件可读是文件内容可以看,可以用cat,more,less
目录可读是目录可以浏览,可以执行ls,tree
文件可以写,可以修改文件内容
目录的写权限是可以添加删除目录项,执行mv,touch,mkdir
文件可执行是可以运行产生一个进程,“./文件名”
目录的执行权限是可以被进入,没有执行权限cd不进去
目录操作函数
opendir
根据传入的目录名打开一个目录(库函数)
DIR*opendir(const char *name);
成功返回指向该目录的结构体指针,失败返回NULL
参数支持相对路径,绝对路径两种方式:例如:打开当前目录
1.getcwd(),opendir()
2.opendir(".")
closedir
关闭打开的目录
int closedir(DIR *dirp);
成功:0
失败:-1 设置errno
readdir
读取目录(库函数)
‘ struct dirent *readdir(DIR *dirp);
成功返回目录项结构体指针
失败返回NULL设置errno为相应值
readwinder
回卷目录读写位置至起始
void rewinder(DIR *dirp)
返回值:无
telldir/seekdir
获取目录读写位置
long telldir(DIR *dirp);成功:与dirp相关目录当前读写位置。
失败-1,设置errno
修改目录读写位置
void seekdir(DIR *dirp,long loc)
返回值:无
参数loc一般由telldir函数的返回值来决定
递归遍历目录思路分析
ls-R.c
1.判断命令行参数,获取用户查询的目录名。argv[1]
argc==1-->./
2.判断用户指定的是否是目录 stat S_ISDIR();
3.读目录:
opendir(dir)
while(readdir()){
普通文件:直接打印
目录:拼接目录访问绝对路径 dir/d_name(sprintf(path,“%s/%s”,dir,dir_name))
递归调用自己。-->opendir(path) readir closedir
}
closedir()
递归遍历目录代码预览
1.
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<pthread.h> #include<sys/stat.h> #include<dirent.h> void isFile(char *name) //打开目录,读取目录,处理目录 void read_dir(char *dir,void(*func)(char *)) { • char path[256]; • DIR *dp; • struct dirent *sdp; • dp=opendir(dir); • if(dp==NULL){ • perror("opendir error"); • return; • } • //读取目录项 • while(sdp=readdir(dp)!=NULL){ • if(strcmp(sdp->d_name,".")==0||strcmp(sdp->d_name,"..")==0){ • continue; • } fprintf(dp); • //目录项目本身不可访问,拼接目录/目录项 • sprintf(path,"%s/%s,dir,sdp->d_name) • //判断文件类型,目录递归进入,文件显示名字/大小 • isFile(path); (*func)(path); • } • closedir(dp); • return; } void isFile(char *name) { • int ret=0; • struct stat sd; • //获取文件属性,判断文件类型 • ret=stat(name,&sb); • if(ret==-1){ • perror("stat error"); • return; • } • //是目录文件 • if(S_ISDIR(sb.st_mode)){ • read_dir(name); • } • //是普通文件,显示名字/大小 • printf("%s\t%d\n",name,sb.st_size); • return; } int main(int argc,char *argv[]) { //判断命令行参数 if(argc==1){ isFile("."); } else{ isFile(argv[1]); } }
2.stat函数原型
int stat(const char *path,struct stat *buf)
dup和dup2
1.dup英文为重复
2.cat myls.c > out
3.dup
int dup(int oldfd);
oldfd:已有文件描述符
返回:新文件描述符
头文件:#include<unistd.h>
4.
#include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include<string.h> #include<unistd.h> #include<pthread.h> int main(int argc,char *argv[]) { fd=open(argv[1],O_RDONLY); int newfd=dup(fd); printf("newfd=%d",newfd); return 0; }
5.dup2(dupto)
int dup2(int oldfd,int newfd);
#include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include<string.h> #include<unistd.h> #include<pthread.h> int main(int argc,char *argv[]) { fd1=open(argv[1],O_RDONLY); fd2=open(argv[2],O_RDONLY); int fdret=dup2(fd1,fd2);//返回文件描述符fd2 printf("fdret=%d\n",fdret); int ret=write(fd2,"1234567"); //写入fd1指向的文件 printf("ret=%d\n",ret); dup2(fd1,STDOUT_FILENO); //将屏幕输入,重定向给fd1所指向的文件 printf("ghjkjhghjikjhg\n"); return 0; }
6.cat hello.c > out
将应该输出在终端的数据写入文件
7.dup2可以理解为后指向前,newfd指向oldfd
fcntl实现dup描述符
1.
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<fcntl.h> #include<unistd.h> #include<pthread.h> int main(int argc,int *argv[]) { • int fd1=open(argv[1],O_RDWR); • printf("fd1=%d\n",fd1); • int newfd=fcntl(fd1,F_DUPFD,0); • printf("newfd=%d\n",newfd); }