一、lseek()
#include <unistd.h>
#include <sys/types.h>
off_t lseek(int filedes, off_t offset, int whence);
//whence取值可选0(SEEK_SET),1(SEEK_CUR),2(SEEK_END);
//返回值:新的偏移量(成功),-1(失败)
如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。这就是所谓的在文件里创造“空洞(hole)”。没有被实际写入文件的所有字节由重复的 0 表示。空洞是否占用硬盘空间是由文件系统(file system)决定的。
二、dup(), dup2(),fcntl()
#include <unistd.h>
int dup(int oldfd);
dup用来复制参数oldfd所指的文件描述符。当复制成功是,返回最小的尚未被使用过的文件描述符,若有错误则返回-1.错误代码存入errno中。返回的新文件描述符和参数oldfd指向同一个文件,这两个描述符共享同一个数据结构,共享所有的锁定,读写指针和各项全现或标志位。dup(oldfd)等价于fcntl(oldfd, F_DUPFD, 0)
#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main(int argc, char* argv[])
{
int fd=open("text.txt", O_CREAT|O_RDWR|O_TRUNC, S_IRUSR|S_IWUSR);
if(fd < 0)
{
printf("Open Error!\n");
return 0;
}
int fd2=dup(fd);
if(fd2<0)
{
printf("Error!\n");
return 0;
}
char buf[1000];
int n;
while((n=read(STDIN_FILENO, buf,1000)) > 0) //接受键盘输入,并将其存入buf所指向的缓存中
{
if(write(fd2, buf, n)<n) //将buf所指向的缓存区的n个字节的数据写入到由文件描述符fd2所指示文件中
{
printf("Write Error!!\n");
return 0;
}
}
return 0;
}
运行结果:
#include <unistd.h>
int dup2(int oldfd,int newfd);
返回:若成功则为非负的描述符,若出错则为-1
dup2函数复制描述符表项oldfd到描述符表项newfd,覆盖描述符表项 newfd以前的内容。如果newfd已经打开了,dup2会在复制oldfd之前关闭newfd;
dup2(4,1)前和后,文件b的引用计数增加了
#include "csapp.h"
int main(int argc, char *argv[])
{
int fd1, fd2, fd3;
char c1, c2, c3;
char *fname = argv[1];
fd1 = Open(fname, O_RDONLY, 0);
fd2 = Open(fname, O_RDONLY, 0);
fd3 = Open(fname, O_RDONLY, 0);
dup2(fd2, fd3);
Read(fd1, &c1, 1);
Read(fd2, &c2, 1);
Read(fd3, &c3, 1);
printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3);
Close(fd1);
Close(fd2);
Close(fd3);
return 0;
}
运行结果:
在Open()获得3个文件描述符之后,创建了3个打开文件表,但都指向同一个v-node表。dup2(fd2,fd3)之后fd3描述符表项指向的打开文件表表项变成fd2所指向的打开文件表表项,然后他们共享文件位置,对不同描述符的读操作可以从文件的不同位置获取数据。
三、close()
#include<unistd.h>
int close(int fd);//成功返回0,出错返回-1
#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main(int argc, char* argv[])
{
int fd=open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 0666);
printf("success=%d\n",fd);
close(fd);
printf("closed\n");
return 0;
}
结果:
这个因为我命令行没有带文件名,所以open出错返回-1,看到fd=-1;
这个在当前目录下新建了a文件,因为0,1,2,分别表示标准输入,标准输出,标准错误,所以fd返回的是3.
可以看到新建的a的权限并不是666而是644,这是因为屏蔽位为022
如果在源程序里面加入umask(0000)就能得到0666的新文件访问权限
四、重定位
//把文件描述符3重定向到标准输出屏幕
exec 3>&1
//把标准输出重定向到test文件,接下来两行echo输出都会在test文件输出
exec 1>test
echo "这句话被存到test文件中"
echo "还有这句"
//然后再次把文件描述符1定位到屏幕
exec 1>&3
echo "这句话输出到显示器"
自定义的文件描述符因为不像描述符1,所有的输出都会自然找它,然后看它是定向到显示器还是某个文件。所以当我们想找描述符6的时候我们要用&来引用它。其实我们可以把文件描述符想像成一个文件的引用,它可以指向任何一个文件(包括显示器),指向的过程就是我们修改默认位置的过程。而用&符号来找到它指向的目标文件,从而向其写入数据。
五、read 和父子进程共享文件
int main(int argc, char *argv[])
{
int fd1;
int s = getpid() & 0x1;
char c1, c2;
char *fname = argv[1];
fd1 = Open(fname, O_RDONLY, 0);
Read(fd1, &c1, 1);
if (fork()) {
/* Parent */
sleep(s);
Read(fd1, &c2, 1);
printf("Parent: c1 = %c, c2 = %c\n", c1, c2);
} else {
/* Child */
sleep(1-s);
Read(fd1, &c2, 1);
printf("Child: c1 = %c, c2 = %c\n", c1, c2);
}
return 0;
}
头文件:#include <unistd.h>
定义函数:unsigned int sleep(unsigned int seconds);
函数说明:sleep()会令目前的进程暂停, 直到达到参数seconds 所指定的时间, 或是被信号所中断.
返回值:若进程/线程挂起到参数所指定的时间则返回0,若有信号中断则返回剩余秒数。
之前没看到int s = getpid() & 0x1;然后在想如果sleep(1-s)是负数怎么办,然后看到sleep的参数是unsigned的会强转
这个打印出来了pid号
原因:在fork之后,子进程有父进程描述符表的副本,父子进程共享相同的打开文件表集合,因此共享相同的文件位置。
六、write()
#include "csapp.h"
int main(int argc, char *argv[])
{
int fd1, fd2, fd3;
char *fname = argv[1];
fd1 = Open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
Write(fd1, "pqrs", 4);
fd3 = Open(fname, O_APPEND|O_WRONLY, 0);
Write(fd3, "jklmn", 5);
fd2 = dup(fd1); /* Allocates new descriptor */
Write(fd2, "wxyz", 4);
Write(fd3, "ef", 2);
Close(fd1);
Close(fd2);
Close(fd3);
return 0;
}
/*abcde.txt
*/
结果:
abcde文件之前内容是:abcde
之后为:
先Open了abcde文件,因为flags参数加上了O_TRUNK,意思是如果文件存在就把文件清空,并把指针指向的当前文件位置改为文件开头。( S_IRUSR|S_IWUSR设置了该文件的权限是当前用户可读可写。)
然后再abcde文件当中写上了4个字符,文件位置是4
pqrs
然后fd3是以APPEND方式打开文件:每次在写操作之前,设置文件位置到文件的结尾处,所以Write(fd3, "jklmn", 5)在文件后加上jklmn,
pqrsjklmn
fd2和fd1指向相同的打开文件表表项,在文件位置4之后写上wxyz,覆盖了之前的jklm
pqrswxyzn
fd3设置文件位置到文件的结尾处,在后面加上ef
pqrswxyznef
七、C语言输出缓冲区
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("h");
printf("e");
printf("l");
printf("l");
printf("o");
printf("\n");//1
fflush(stdout);//2
exit(0);
}
结果:
第一次什么都没干
第二次把2注释掉了
第三次把1注释掉了
第四次把1、2都注释掉了
unix上标准输入输出都是带有缓存的,一般是行缓存。
对于标准输出,需要输出的数据并不是直接输出到终端上,而是首先缓存到某个地方,当遇到行刷新标志或者该缓存已满的情况下,才会把缓存的数据显示到终端设备上。
ANSI C中定义换行符'\n'可以认为是行刷新标志。所以,printf函数没有带'\n'是不会自动刷新输出流,直至缓存被填满。
但fflush(stdout)刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上 。
在进程退出时也会清空标准输出缓冲区
八、stat()
stat函数检索文件的信息(文件的元数据)
#include<unistd.h>
#includ<sys/stat.h>
int stat(const char* filename,struct stat *buf);
int fstat(int fd,struct stat *buf);
返回:若成功则为0,出错-1
/* $begin statcheck */
#include "csapp.h"
int main (int argc, char **argv)
{
struct stat stat;
char *type, *readok;
/* $end statcheck */
if (argc != 2) {
fprintf(stderr, "usage: %s <filename>\n", argv[0]);
exit(0);
}
/* $begin statcheck */
Stat(argv[1], &stat);
if (S_ISREG(stat.st_mode)) /* Determine file type */
type = "regular";
else if (S_ISDIR(stat.st_mode))
type = "directory";
else
type = "other";
if ((stat.st_mode & S_IRUSR)) /* Check read access */
readok = "yes";
else
readok = "no";
printf("type: %s, read: %s\n", type, readok);
exit(0);
}
结果: