1、系统调用和标准IO库函数的区别
库是可重用的模块 处于用户态
系统调用是os提供的服务 处于内核态 不能直接调用 而要使用类似int 0x80的软中断陷入内核 所以库函数中有很大部分是对系统调用的封装。从宏观上说,系统调用时内核层,C标准库在应用层。
2、文件描述符
文件描述符是Linux内核为了高效管理已被打开的文件而创建的索引,其值为一个非负整数,而通常为小整数。而且系统运行时,系统会自动打开三个文件描述符,即标准输入0,标准输出1,标准 出错2 。这三个文件描述符默认存在,若新打开的文件描述符最小只能是3 .
3、文件扩展名
在Linux中,扩展名对Linux内核没有实际意义,但是可以用来人为区分不同的文件,方便用户使用。
.tar, .tar.gz, .tgz, .zip, .tar.bz表示压缩文件,
创建命令为tar, gzip, unzip等
.sh 文件表示shell脚本文件
.pl 表示perl语言文件
.py 表示python语言文件
.conf 表示系统服务的配置文件
.c 表示C文件
.h 头文件
.cpp 表示C++源文件
.so 表示动态库文件
.a 表示静态库文件
4、文件I/O操作函数
open()系统调用
int open(const char *path, int oflag, ... /*mode_t mode*/);
open()系统调用用来打开一个文件,并返回一个文件描述符,并且该文件描述符是当前进程最小、未使用的文件描述符数值。
参数:path:要打开的文件、设备的路径
oflag: 由多个选项进行“或”运算构造oflag参数。
必选:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)。
可选: O_APPEND 每次写时都追加到文件的尾端。
O_CREAT 文件不存在则创建它,使用该选项需要第三个参数mode
O_TRUNC 如果文件存在,而且为只写或读写成功打开,则将其长度截取为0;
O_NONBLOCK 如果path是一个FIFO、块设备、字符特殊文件则此选项为文件的本次打开和后续的I/O操作 设置非阻塞模式方式。
代码示例:
int fd;
fd = open("text.txt", O_RDWR|O_CREAT|O_TRUNC, 0666);
fd = open("text.txt", O_WRONLY|O_APPEND);
create()系统调用
int create(const char *path, mode_t mode);
该函数用来创建一个新文件并返回其fd。它等价于open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
int fd;
fd = create("text.txt", 0644);
write()系统调用
ssize_t write(int fd, const void *buf, size_t nbytes);
write()函数用来往打开的文件描述符fd指向的文件中写入buf指向的数据,其中nbytes指定要写入的数据大小。如果返回值 <0则说明写入出错,譬如尝试往一个只读的文件中写入则会抛错,错误的原因系统会保存到errno变量中去。如果>0则为实 际写入的数据大小。
lseek()系统调用
off_t lseek(int fd, off_t offsset, int whence);
我们在从文件里读出内容,或往文件写如内容的时候都有一个起始地址,这个起始地址就是当前文件偏移量,当我们对文件 进行读写的时候都会使文件偏移量往后偏移。这点就类似于我们打开记事本开始编辑文本时的光标,我们读或写入时从光标 所在位置开始读写,每读写一个字节都会使光标往后偏移。通过lseek()这个函数我们可以调整文件偏移量的地址。 其中 whence 可以是以下三个值:
而offset就是相对于whence 的偏移量,
譬如
lseek(fd, 0, SEEK_SET); 将文件偏移量设置到了文件开始的第一个字节上
lseek(fd, 0, SEEK_END); 将文件偏移量设置到文件最后一个字节上;
lseek(fd, -1, SEEK_END); 将文件偏移量设置到文件最后的倒数第一个字节上;
read()系统调用
ssize_t read(int fd, void *buf, size_t nbytes);
read()函数用来从打开的文件描述符对应的文件中读取数据放到buf指向的内存空间中去,最多不要超过nbytes个字节,这里 的nbytes一般是buf剩余的空间大小。如read成功,则返回实际读到的字节数(由nbytes或读到文件尾决定,其中EOF宏用 来判断是否到了文件尾),如果返回值小于0则表示出错,如尝试读一个没有权限读的文件时就会抛错。
出错处理
对Linux而言绝大部分情况下,系统调用出错都会返回-1,而成功则返回0或其相应返回值。Linux中系统调用的错误原因都 存储于在int errno中,errno由操作系统维护,存储就近发生的错误,即下一次的错误码会覆盖掉上一次的错误,当然只有在 系统调用或者调用lib函数时出错(因为有些库函数会调用系统调用),才会置位errno。在头文件“/usr/include/asmgeneric/errno-base.h”定义了常见的错误原因errno整形值。大家可以看看这个头文件的定义,了解一下常见的错误原因。
因为系统调用出错的原因是以整形值的形式保存在errno中,则个整形值errno对程序员非常不友好,我们更多的是希望看 到字符串形式的错误提示,这时经常用到的两个错误处理函数是 perror()和strerror()。下面是这两个函数的原型。
void perror(const char *s)
char *strerror(int errno);
其中perror()的使用方法非常简单,只需要一个字符串参数s即可。他会将错误的原因打印到标准输出上,其内容是字符串s 后面紧跟 冒号和字符串形式的错误原因。需要注意的是字符串s 里不要加换行符,因为错误原因里面会带换行。 因为perror()的使用功能比较简单且只能打印到标准输出上,对于要进行格式化控制的输出或想写入到日志文件中时就不能 处理了。这时我们会使用另外一个函数strerror(),它用来将整形类型的错误原因errno装换成相应的字符串形式,这样在任 何使用字符串的地方我们都可以使用它。所以在今后的编程中,我们更多的是使用strerror()而不是perror()。
5、代码展示
该程序将调用open()系统调用打开一个叫做test.txt的文件(如 果不存在则会创建该文件),然后调用write()系统调用将字符串 MSG_STR 写入到该文件中,之后调用read()系统调用读出该 文件里的内容。
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define BUFSIZE 1024
#define WXJ "Helo xiaojuan\n"
int main(int argc, char **argv)
{
int fd = -1;
int rv = -1;
char buf[BUFSIZE];
fd = open("test.txt", O_RDWR|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("Open/Create file test.txt failture");
/*错误处理函数perror,void perror(const *s)
它会将错误的原因打印到标准输出上*/
return 0;
}
printf("Open file returned file descriptor [%d]\n",fd);
if((rv = write(fd, WXJ, strlen(WXJ))) < 0)
{
printf("Write %d bytes into file failture:%s\n",rv,strerror(errno));
goto cleanup;
}
memset(buf, 0, sizeof(buf)); //清零buf
lseek(fd, 0, SEEK_SET);
if((rv = read(fd, buf, sizeof(buf))) < 0)
{
printf("Read data from file failture:%s\n", strerror(errno));
goto cleanup;
}
printf("Read %d bytes data from file:%s\n", rv, buf);
cleanup:
close(fd); //用完的文件描述符记得关闭
return 0;
}
程序运行截图:
由结果可以看出,新打开的文件描述符为3,为当前可用最小。写程序时应注意将文件偏移量移到文件开头去,方可读到数据,而且写完数据后应当关闭文件描述符,以便下次使用。