系统调用与库函数,底层文件访问系统调用
系统调用和库函数与部分常见的底层文件访问系统调用
1.系统调用与库函数的区别
系统调用是操作系统内核提供的函数,在内核态运行。直接使用系统调用的效率非常低,因为
1.使用系统调用会影响系统的性能,在执行系统调用时,操作系统必须从用户态切换到内核态,然后再返回用户态。
频繁的切换会消耗大量的资源(需要保存上下文信息)。所以,系统调用的开销会很大。
2.硬件会限制底层调用一次能读写的数据块大小,例如,磁带机一次能读写10k,如果你的数据量不是10k的倍数,磁
带机仍然以10k为单位卷绕磁带,从而留下空隙。
库函数提供了一层对系统调用的封装,使得函数调用更方便,安全以及高效。虽然调用库函数,最终还是要调用系统调用,
但库函数会对系统调用的组织方式要比我们对自己调用方式好的多,很对库函数会对调用的执行进行优化。例如malloc,malloc
的本质也是要调用系统调用才能分配内存,但malloc的实现机制使用了一系列的数据结构来组织和维护申请内存的操作,当然会
比使用者直接调用系统函数高效,安全的多。
2.常见的底层文件系统调用
1.write
首先,看一下write的原型
#include <unistd.h> size_t write(int fildes,const void *buf,size_t nbytes);
系统调用write的作用是将缓冲区buf的前nbyte个字节写入到与文件描述符filedes相关联的文件中。返回值为实际写入的字节数,
如果返回0,表示未写入数据。如果返回-1,表示调用出错,错误代码保存在全局变量errno中。
#include <unistd.h> #include <stdlib.h> int main() { if(write(1,"Here is some data\n",18)) write(2,"A write error has occurred on file descriptor 1\n",46); exit(0); }
执行./write
Here is some data
在write调用中,文件描述符部分设置为1和2的原因是,当一个程序开始运行时,它会默认打开三个文件描述符:
0 代表标准输入; 1 代表标准输出;2 代表标准错误。
3.read
read的原型
#include <unistd.h> size_t read(int fildes,void *buf,size_t nbytes);
系统调用read的作用是从文件描述符fildes相关联的文件中读取nbytes个字节的数据,并将数据放到数据区buf中,返回值为实际读
入的字节数,如果返回值为0,表示未读入任何数据,已到达文件结尾,返回值为-1,表示调用出错。
#include <unistd.h> #include <stdlib.h> int main() { char buffer[128]; int nread; nread = read(0,buffer,128); if(nread == -1) write(2,"A read error has occurred\n",26); if((write(1,buffer,nread)) != nread) write(2,"A write error has occurred\n",27); exit(0); }
执行 echo hello there | ./read
hello there
echo命令将字符串写入到标准输入中,read函数从标准输入中读入字符串放到buf中,write将buf写入到标准输出中,标准输出
中的内容显示在终端上。
4.open
系统调用open用于创建一个新的文件描述符,它的原型如下:
#include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> int open(const char *path,int oflags); int open(const char *path,int oflags,mode_t mode);
path为文件或设备的名字,oflags为打开文件的动作,主要有
O_RDONLY : 只读打开
O_WRONLY : 只写打开
O_RDWR : 读写方式打开
看一个栗子:
#include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h>
#include <stdio.h> int main() { int fd = open("./myfile",O_RDWR); if(fd == -1) write(2,"A open error",12); else write(fd,"A open sucessed\n",16); //printf("%d\n",fd); exit(0); }
执行./open,再执行 cat myfile
A open sucessed
可以看到,我们使得fd获得了myfile的文件描述符,这时,可以通过fd来进行对myfile的操作。
放开那行注释,可以看到fd的值为3。为什么呢?文件描述的产生是随机的吗?并不是,前面提到过
程序会默认打开三个文件描述符0,1,2。在我们调用open产生新的文件描述符时,系统会检查已存在的
文件描述符,然后新的文件描述符的值为除过已存在的文件描述符的最小整数。通过close系统调用的讲解
我们来进一步了解。
open调用可以再oflages参数中包含可选模式的组合:
O_APPEND : 以追加的方式写入数据
O_TRUNC : 设置文件长度为0,丢弃已有内容
O_CREAT : 如果需要,按照参数mode创建文件
O_EXCL : 与O_CREAT一起使用,如果文件存在,创建失败。
当我们使用O_CREAT参数来调用open时,要使用有三个参数的open调用。第三个参数指定创建文件的权限。举个栗子
int main() { int fd = open("./myfile2",O_RDWR | O_CREAT | O_EXCL,S_IRUSR | S_IWUSR | S_IWOTH); if(fd == -1) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); }
执行./open
执行 ll myfile2
-rw-------. 1 zhaozhao zhaozhao 0 3月 29 17:48 myfile2
文件被创建,而且主用户读写权限被设置,但我们在程序中还设置了其他用户写权限,被没有产生这个权限。
实际上,创建文件时,open中给出的权限设置受到umask值的限定。
5.close
系统调用close可以关闭文件描述符,它的原型为
#include <unistd.h> int close(int fildes);
调用成功返回值为0,出错为-1.看一个栗子
#include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> int main() { if(close(1) == -1) exit(EXIT_FAILURE); int fd = open("./myfile",O_RDWR); if(write(1,"A close sucessed\n",16) != 16) write(2,"A write error",13); printf("%d\n",fd); exit(EXIT_SUCCESS); }
执行./close后,终端无任何显示
执行cat myfile
A close sucessed
1
结果分析:
调用close关闭了1号文件描述符,这是文件描述符1不再代表标准输出,实际上此时文件描述符1不存在
调用open为myfile创建文件描述符,系统会查询此时已存在的文件描述符,此时0,2存在,新的文件描述符
就是1。在上一个讲解open时的例子里面,文件描述符0,1,2存在,所以新的文件描述符为3。如果再调用
open,新的文件描述符会是4。这就是上文所说的很绕口的那句话的意思。然后新的文件描述符为除过已存在的
文件描述符的最下整数。那么printf的结果又是怎么回事呢?实际上,printf执行输出操作,内部调用了write(1,str,n)
操作!!!我们使得文件描述符1成为了myfile的文件描述符,那么printf的结果不再进入标准输出缓冲区,而是进
入文件myfile中。
6.usmak
usmak是一个系统变量,也是一条命令,同时它也是一个系统调用函数。
作为一个系统变量,它的作用是:当文件被创建时,为文件的访问权限设定一个掩码。它的值为xxxx;umask的
后三位代表用户主,组用户,其他用户的权限,当我们创建文件时,umask对应的位置如果有效,则文件不会拥有该权限
比如,我的umask为0002,那么,我创建一个新文件时,新文件绝不会有其他用户写权限。
umask作为一个命令时,可以查看当前系统的umask值,添加 -p选项,后面跟上新的umask值,可以修改umask。
作为系统调用时,原型为
#include <sys/stat.h>
mode_t umask(mode_t cmask);
我们可以再程序中改变umask值,使得它不再影响我们创建新文件时的权限。修改open部分的代码
int main() { umask(0000); int fd = open("./myfile2",O_RDWR | O_CREAT | O_EXCL,S_IRUSR | S_IWUSR | S_IWOTH); if(fd == -1) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); }
执行./open
执行ll myfile2
-rw-----w-. 1 zhaozhao zhaozhao 0 3月 29 18:27 myfile2
这时,新文件的权限完全是我们想要的。