今天我们来看一下几个系统调用函数,以及他们的基本用法。
一、open
int open ( char *filename , int flags , mode_t mode );
open 函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。
①filename
文件名
②flags
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_RDWR | 可读可写 |
O_CREAT | 如果文件不存在,就创建它的一个空文件 |
O_TRUNC | 如果文件已经存在,就清空它 |
O_APPEND | 在每次写操作前,设置文件位置到文件的结尾处,也就是追加操作 |
flags参数也可以是一个或者更多位掩码的或,为写提供给一些额外的提示
③mode
mode参数制定了新文件的访问权限位
掩码 | 描述 |
S_IRUSR S_IWUSR S_IXUSR |
使用者(拥有者)能够读这个文件 使用者(拥有者)能够写这个文件 使用者(拥有者)能够执行这个文件 |
S_IRGRP S_IWGRP S_IXGRP |
拥有者所在组的成员能够读这个文件 拥有者所在组的成员能够写这个文件 拥有者所在组的成员能够执行这个文件 |
S_IROTH S_IWOTH S_IXOTH |
其他人(任何人)能够读这个文件 其他人(任何人)能够读写这个文件 其他人(任何人)能够执行这个文件 |
下面的函数说明如何以读的方式打开一个已存在的文件
int fd=open("foo.txt",O_RDONLY,0);
下面的函数说明如何打开一个已存在的文件,并在后面添加一些数据
int fd=open("foo.txt",O_WRONLY|O_APPEND,0);
下面的函数经常用作创建一个新文件
int fd=open("foo.txt",O_CREAT|O_TRUNC|O_WRONLY,0);
二、read、write
ssize_t read ( int fd , void *buf , size_t n );
read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,而返回值0表示EOF。否则,返回值表示的实际传送的字节数量。
ssize_t write ( int fd, const void *buf , size_t n );
write函数从内存位置buf复制最多n个字节到描述符fd的当前文件位置。
下面的程序使用read和write调用一次一个字节的从标准输入(键盘)复制到标准输出(屏幕)。
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
char c;
while(read(STDIN_FILENO,&c,1)!=0)
write(STDOUT_FILENO,&c,1);
exit(0);
}
敲一个字符,按下回车就显示
三、lseek
off_t lseek ( int fd , off_t offset , int whence );
lseek 定位一个文件,返回当前光标距文件开头的字节数
offset | 偏移量 |
SEEK_SET | 参数offset 即为新的读写位置. |
SEEK_CUR | 以目前的读写位置往后增加offset 个位移量. |
SEEK_END | 将读写位置指向文件尾后再增加offset 个位移量. 当whence 值为SEEK_CUR 或SEEK_END 时, 参数offet 允许负值的出现. |
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
/**
abc.text文件里存放一个字符串“abcdefghijklmn”
*/
int fd=open("abc.text",O_RDONLY,0);
int buf[20];
int num[10];
int buf_size=read(fd,buf,20);
printf("buf_size=%d",buf_size);
//把光标设置到偏移量为2的地方
int current_position=lseek(fd,2,SEEK_SET);
printf("current_position=%d",current_position);
//将光标后移3位
int new_position_1=lseek(fd,3,SEEK_CUR);
printf("new_position_1=%d",new_position_1);
//读文件3个字节,输出到屏幕上
read(fd,num,new_position_1-current_position);
write(STDOUT_FILENO,num,new_position-current_position);
printf("\n");
//以文件末尾为基准,向前移1位
int new_position_2 =lseek(fd,-1,SEEK_END);
pritnf("new_position_2=%d",new_position_2);
//把光标置于文件末尾,用这种办办法也可以算作求出文件大小
int end_position=lseek(fd,0,SEEK_END);
printf("end_position=%d",end_position);
}
来看一下运行结果,读字节的时候,可以发现文件的光标真的有在移动。
四、stat
应用程序能够通过调用stat函数,检索到关于文件的信息,有时也称为文件的元数据
int stat (const char *filename , struct *buf );
结构体buf有13个成员,我们简单说两个常用的即可。st_size,表示文件字节数大小。st_mode成员则编码了文件访问许可位和访问类型。Linux再sys/stat.h中定义了宏谓词来确定st_mode成员的文件类型,
S_ISREG(m) | 这是一个普通文件么? |
S_ISDIR(m) | 这是一个目录文件么 |
S_ISSOCK(m) |
这是一个网络套接字么? |
接下来看一个小代码,如何利用st_mode查看一个文件权限和类型
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
struct stat s;
char *readok,*writeok,*exeok;
stat(argv[1],&s);
if(S_ISREG(s.st_mode))//普通文件
type="regular";
else if(S_ISDIR(s.st_mode))//目录
type="directionary";
else type="others";
//查看读权限
if((s.st_mode&S_IRUSR))
readok="yes";
else readok="no";
//查看写权限
if((s.st_mode&S_IWUSR))
writeok="yes";
else writeok="no";
//查看执行权限
if((s.st_mode&S_IXUSR))
exeok="yes";
else exeok="no";
printf("type\treadok\twriteok\texeok\n");
printf("%s\t%s\t\%s\t%s\n",type,readok,writeok,exeok);
}
五、共享文件
可以用许多不同的方式来共享Linux。内核用三个相关的树结构来表示打开的文件。
描述符表 | 每个进程都有它独立的描述符表,它的表项是由结成打开的文件描述符来索引的。每个进程都有它独立的描述符表项指向文件表中的一个表项 |
文件表 | 打开文件的集合是由一张文件表来表示的,所有进程共享这张表 |
v-node表 | 同文件表一样,所有的进程共享这张v-node表。每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员。 |
①下图展示了一个示例,其中描述符1和3通过不同给的打开文件表表项来引用两个不同的文件。这是一个典型的情况,没有共享文件,并且每个描述符对应一个不同的文件。
大致的代码
int fd1=open("abc.text",O_RDONLY,0);
int fd3=open("123.text",O_RDONLY,0);
② 多个描述符也可以通过不同的文件表表项来引用同一个文件。例如,如果同一个filename调用open函数两次,就会发生这种情况。关键思想是每个描述符都有它自己的文件位置,所以对不同描述符的读操作可以从文件的不同位置获取数据。
大致的代码
int fd1=open("abc.text",O_RDONLY,0);
int fd3=open("abc.text",O_RDONLY,0);
③父子进程共享相同的文件表集合,因此共享相同的文件位置。一个最重要的结果就是,在内核删除相应文件表表项之前,父子进程必须都关闭了他们的描述符。
一些小练习
①
//假设123.text文件中存放的内容是123456790
int fd1=open("abc.text",O_RDONLY,0);
int fd2=open("123.text",O_RDONLY,0);
int buf1[20],buf2[20];
read(fd1,buf1,20);
write(STDOUT_FILENO,buf1,20);
read(fd2,buf2,20);
write(STDOUT_FILENO,buf2,20);
他的结果是简单的。对应了第一种情况,打开了两个不同的文件,都是从头输出.。
②
//假设我们读的文件中的内容是 abcdefgh
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,简单来说这个函数的效果就是凡是对fd3的操作都可以看做是对fd2的操作
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;
}
由于dup2,对fd3的操作都相当于对f2的操作。c1读到fd1中的a,c2读到了fd2中的a,此时fd2中的光标在‘a’的后面,所以c3读到了fd2中的b。
③
int main(int argc, char *argv[])
{
int fd1, fd2, fd3;
char *fname = argv[1];
//创建一个名为fname的新文件,可读可写,在里面写入pqrs
fd1 = Open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
Write(fd1, "pqrs", 4);
//以追加写的方式打开刚刚创建号的fname的文件,在里面写入jklmn
fd3 = Open(fname, O_APPEND|O_WRONLY, 0);
Write(fd3, "jklmn", 5);
//dup,使之后对fd2的操作都变为对fd1的操作
fd2 = dup(fd1); /* Allocates new descriptor */
Write(fd2, "wxyz", 4);
Write(fd3, "ef", 2);
Close(fd1);
Close(fd2);
Close(fd3);
return 0;
}
最终它的运行结果是 pqrswxyznef ,我们来看一下他的实现过程。
④
//假设读取的文件中存放的是abcdefgh
int main(int argc, char *argv[])
{
int fd1;
int s = getpid() & 0x1;
printf("s=%d\n",s);
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;
}
可以看到,子进程打印结束后,大概等待一秒,父进程才打印出来。,read在fork之前进行了第一次读,c1没有争议的读到了a,此时光标在‘a’后。子进程因为没有sleep,从而先比父进程先读,读到了b,此时光标在‘b’后。父进程sleep结束后,读取c2,也就读到了c。