C标准函数与系统函数的区别
什么是系统调用
由操作系统实现并提供给外部应用程序的编程接口。(Application Programming Interface,API)。是应用程序同系统之间数据交互的桥梁。
一个helloworld如何打印到屏幕。
每一个FILE文件流(标准C库函数)都有一个缓冲区buffer,默认大小8192Byte。Linux系统的IO函数默认是没有缓冲区.
open/close
文件描述符
一个进程启动之后,默认打开三个文件描述符:
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
新打开文件返回文件描述符表中未使用的最小文件描述符, 调用open函数可以打开或创建一个文件, 得到一个文件描述符.
open函数
函数描述: 打开或者新建一个文件
函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函数参数:
- pathname参数是要打开或创建的文件名,和fopen一样, pathname既可以是相对路径也可以是绝对路径。
- flags参数有一系列常数值可供选择, 可以同时选择多个常数用按位或运算符连接起来, 所以这些常数的宏定义都以O_开头,表示or。
- 必选项:以下三个常数中必须指定一个, 且仅允许指定一个。
- O_RDONLY 只读打开
- O_WRONLY 只写打开
- O_RDWR 可读可写打开
- 必选项:以下三个常数中必须指定一个, 且仅允许指定一个。
- 以下可选项可以同时指定0个或多个, 和必选项按位或起来作为flags参数。可选项有很多, 这里只介绍几个常用选项:
O_APPEND 表示追加。如果文件已有内容, 这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode, 表示该文件的访问权限。
- 文件最终权限:mode & ~umask
- Umask 指令查看文件掩码
- O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
- O_TRUNC 如果文件已存在, 将其长度截断为为0字节。
- O_NONBLOCK 对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O(NonblockI/O),非阻塞I/O。
- 0777 表示八进制 最高权限 三组权限
- Mode 设置777 umask默认为0002 最后权限结果为775
函数返回值:
- 成功: 返回一个最小且未被占用的文件描述符
- 失败: 返回-1, 并设置errno值.
close函数
- 函数描述: 关闭文件
- 函数原型: int close(int fd);
- 函数参数: fd文件描述符
函数返回值
- 成功返回0
- 失败返回-1, 并设置errno值.
需要说明的是,当一个进程终止时, 内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close, 在终止时内核也会自动关闭它打开的所有文件。但是对于一个长年累月运行的程序(比如网络服务器), 打开的文件描述符一定要记得关闭, 否则随着打开的文件越来越多, 会占用大量文件描述符和系统资源。
read/write
read函数
- 函数描述: 从打开的设备或文件中读取数据
- 函数原型: ssize_t read(int fd, void *buf, size_t count);
- 函数参数:
- fd: 文件描述符
- buf: 读上来的数据保存在缓冲区buf中
- count: buf缓冲区存放的最大字节数
- 函数返回值:
- >0:读取到的字节数
- =0:文件读取完毕
- -1: 出错,并设置errno
write
- 函数描述: 向打开的设备或文件中写数据
- 函数原型: ssize_t write(int fd, const void *buf, size_t count);
- 函数参数:
- fd:文件描述符
- buf:缓冲区,要写入文件或设备的数据
- count:buf中数据的长度
- 函数返回值:
- 成功:返回写入的字节数
- 错误:返回-1并设置errno
lseek
所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为cfo. cfo通常是一个非负整数, 用于表明文件开始处到文件当前位置的字节数. 读写操作通常开始于 cfo, 并且使 cfo 增大, 增量为读写的字节数. 文件被打开时, cfo 会被初始化为 0, 除非使用了 O_APPEND.
使用 lseek 函数可以改变文件的 cfo.
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
- 函数描述: 移动文件指针
- 函数原型: off_t lseek(int fd, off_t offset, int whence);
- 函数参数:
- fd:文件描述符
- 参数 offset 的含义取决于参数 whence:
- 如果 whence 是 SEEK_SET,文件偏移量将设置为 offset。
- 如果 whence 是 SEEK_CUR,文件偏移量将被设置为 cfo 加上 offset,offset 可以为正也可以为负。
- 如果 whence 是 SEEK_END,文件偏移量将被设置为文件长度加上 offset,offset 可以为正也可以为负。
- 函数返回值: 若lseek成功执行, 则返回新的偏移量。
- lseek函数常用操作
- 文件指针移动到头部
- lseek(fd, 0, SEEK_SET);
- 获取文件指针当前位置
- int len = lseek(fd, 0, SEEK_CUR);
- 获取文件长度
- int len = lseek(fd, 0, SEEK_END);
- lseek实现文件拓展
- off_t currpos;
- // 从文件尾部开始向后拓展1000个字节
- currpos = lseek(fd, 1000, SEEK_END);
- // 额外执行一次写操作,否则文件无法完成拓展
- write(fd, “a”, 1); // 数据随便写
-
perror和errno
errno是一个全局变量, 当系统调用后若出错会将errno进行设置, perror可以将errno对应的描述信息打印出来.
如:perror("open"); 如果报错的话打印: open:(空格)错误信息
-
每个进程中都有自己独有的error number 他们之间互不影响
-
FileIO操作le
//IO函数测试 open close read write lseek
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *agrv[])
{
//打开文件
//int open(const char *pathname, int flags, mode_t mode);
//第一参数文件名 第二个参数为对文件操作类型 第三个参数为权限
//O_RDONLY 只读打开 O_WRONLY 只写打开 O_RDWR 可读可写打开
//0777 表示八进制权限 777代表最高权限
for (size_t i = 0; i < sizeof(argc); i++)
{
printf("argv = %s\n", agrv[i]);
}
/*从一开始 0存放的是地址
argv = ./open
argv = test.log
argv = (null)
argv = SHELL=/bin/bash
*/
int fd = open(agrv[1], O_RDWR, 0777);
if (fd < 0)
{
//提示出错信息
perror("OPEN error");
return -1;
}
//写文件
//ssize_t write(int fd, const void *buf, size_t count);
//第一参数文件描述符 第二个参数缓冲区 第三个参数写的长度
write(fd, "hello world", sizeof("hello world"));
/*
文件指针移动到头部
lseek(fd, 0, SEEK_SET);
获取文件指针当前位置
int len = lseek(fd, 0, SEEK_CUR);
获取文件长度
int len = lseek(fd, 0, SEEK_END);
从文件尾部开始向后拓展1000个字节
currpos = lseek(fd, 1000, SEEK_END);
额外执行一次写操作,否则文件无法完成拓展
write(fd, “a”, 1); // 数据随便写
*/
//移动文件到文件开始
//off_t lseek(int fd, off_t offset, int whence);
lseek(fd, 0, SEEK_SET);
//读文件
//ssize_t read(int fd, void *buf, size_t count);
//第一参数文件描述符 第二个参数缓冲区 第三个参数读的长度
char buf[1024];
//将数据写入内存
memset(buf, 0, sizeof(buf));
//读文件
int n = read(fd, buf, sizeof(buf));
//输出
printf("n == [%d], buf == [%s]\n", n, buf);
//关闭文件
//关闭文件写在此处 不能输出结果 因为写入后没有关闭文件 直接读文件是空
//close(fd);
return 0;
}
lseek函数的应用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *agrv[])
{
//打开文件
int fd = open(agrv[1], O_RDWR, 0777);
//计算文件长度
int length = lseek(fd, 0, SEEK_END);
printf("file size = %d\n", length);
return 0;
}
gcc + 文件默认会生成a.out文件
./a.out + 文件 代表文件是传进去的参数。
lseek函数扩展文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *agrv[])
{
//打开文件
int fd = open(agrv[1], O_RDWR, 0777);
// //计算文件长度
// int length = lseek(fd, 0, SEEK_END);
// printf("file size = %d\n", length);
//lseek函数实现文件扩展
//移动指针位置到100字节处 SEEK_SET表示从文件开头
lseek(fd, 100, SEEK_SET);
//写入数据
write(fd, "H", 1);
return 0;
}
测试普通文件是否是阻塞
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *agrv[])
{
int fd = open(agrv[1], O_RDWR, 0777);
if (fd < 0)
{
//提示出错信息
perror("OPEN error");
return -1;
}
char buf[1024];
//将数据写入内存
memset(buf, 0, sizeof(buf));
//读文件
int n = read(fd, buf, sizeof(buf));
//输出
printf("First n == [%d], buf == [%s]\n", n, buf);
//第二次读 判断是否阻塞
memset(buf, 0, sizeof(buf));
n = read(fd, buf, sizeof(buf));
printf("Second n == [%d], buf == [%s]\n", n, buf);
}
阻塞和非阻塞:
思考: 阻塞和非阻塞是文件的属性还是read函数的属性?
类似模态对话框和非模态对话框
- 普通文件:hello.c
- 默认是非阻塞的
- 终端设备:如 /dev/tty 标准输入STDIN_FILENO
- 默认阻塞
- 管道和套接字
- 默认阻塞
得出结论: 阻塞和非阻塞是文件本身的属性, 不是read函数的属性.