一、系统API与C库函数的调用关系
当我们在C语言程序中调用一个库函数的时候,比如调用printf()函数,实际上它是通过文件指针来指向要打印的位置的。并且,printf()函数会调用Linux的系统函数write()函数(它是一个系统接口,也可以人工调用),write()函数再继续调用sys_write()函数(这个函数只能是操作系统去调用),sys_write()继续调用设备驱动,具体调用哪个驱动要看输出的位置,如果是printf()打印到显示器上,那么就调用显示器驱动并打印在屏幕上,如果是写到网络上,就会调用网卡驱动。我们所作的只有在C程序中调用printf()等库函数,其余操作都是操作系统帮我们做的。请看下面这张图。
printf()函数在打印的时候通过一个文件指针来实现打印到某个文件的某个位置。在文件在文件指针中,包含了一个文件描述符,这个文件描述符用于指定目标文件,默认情况下就是STDOUT_FILENO也就是标准输出1号描述符;f_pos指定了读写的位置,比如我们打印的时候他会不停的在上一次打印的末尾位置打印后面的内容,就是通过这个位置去实现的;在最后还有一个缓冲区buffer,那么为什么要有buffer缓冲区呢,其实这是为了提高读写的效率,把读写的内容先放到缓冲区,这样就可以实现一次读写更多的内容。并且,这个缓冲区需要刷新才能得到输入输出,我们可以通过下面程序来测试一下。
测试文件01_print_dm.c
代码语言:javascript复制/************************************************************
>File Name : 01_print_dm.c
>Author : QQ
>Company : QQ
>Create Time: 2022年05月12日 星期四 15时28分15秒************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
close(1);
int fd = open("test.log", O_CREAT | O_TRUNC | O_WRONLY, 0644);
printf("hello ... n");
fflush(stdout);
close(fd);
return 0;
}
编译文件makefile
代码语言:javascript复制.PHONY:all clean
CC=gcc
CFLAGS=-Wall -g
EXE=01_print_dm
all:$(EXE)
%.o:%.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
-@rm -f *.o $(EXE)
在这个程序中close(1);表示关闭标准输出,在前面我们已经说过文件描述符1代表标准输出,这时候通过open()打开一个文件,我们知道,当打开一个文件的时候会使用一个当前空闲的最小文件描述符,因为前面我们把标准输出关闭了,所以当前空闲的最小文件描述符1分配给open()函数打开的文件。虽然1号文件描述符当前已经不是标准输出(终端显示屏)了,但是stdout依然是指向1号文件描述符的,实际上这就相当于把open()打开的文件当作标准输入输出,printf()打印的内容都会打印到test.log文件内。首先我们屏蔽fflush()函数试一下
这个test.log文件内是空的,也就是说,如果不刷新缓冲区的话,无法正常打印内容,我们把刷新函数fflush()加上就可以看到,printf()函数打印内容直接打印到test.log文件内了,而不会打印在终端。
实际上,在Linux下启动一个进程,就会默认打开三个文件描述符:0标准输入、1标准输出、2标准错误。它们分别对应C语言中的stdin、stdout、stderr。当我们每次打开一个文件,就会分配给这个文件一个当前空闲的最小文件描述符,如果此时标准输入0、标准输出1、标准错误2空闲,那么也会把这个文件描述符分配给新打开的文件但是这三个文件描述符0、1、2与stdin、stdout、stderr的对象关系不会变,并且在后续的操作中会把0、1、2指向的新文件当作标准输入输出和标准错误去处理,并将输入输出或错误信息打印到这个文件。
在系统API中,主要包含了这些函数:与文件IO相关的函数接口(比如open(),close(),write(),read()等);与文件属性相关的函数;与目录操作相关的函数;与目录遍历相关的函数;还有dup()、dup2()、fcntl()函数等。这些函数都属于系统调用,可以通过命令 man 2 functionname 查看。这里有个小技巧要注意,如果你直接输入 man functionname 没有显示出函数原型等信息,这就说明该函数也有对应的同名命令,这时候可以通过加章节来查看函数说明,比如 man 2 functionname 表示查看第二章,也就是系统调用API。
二、open与close函数
1. open函数
包含头文件
代码语言:javascript复制#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数原型
代码语言:javascript复制int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函数功能
- 打开一个文件,并返回文件描述符。
函数参数
- pathname:文件名
- flags:
- 必选参数(下面三个必须要有一个)
- O_RDONLY :只读
- O_WRONLY :只写
- O_RDWR :可读可写
- 可选参数(仅列出常用参数)
- O_APPEND :追加的方式打开,The file is opened in append mode.
- O_CREAT :如果文件不存在则创建,If the file does not exist it will be created.
- O_EXCL :和O_CREAT一块使用,如果文件存在则报错,if this flag is specified in conjunction with O_CREAT, and pathname already exists, then open() will fail.
- O_NONBLOCK :非阻塞的方式打开文件
- mode:权限位 (实际的权限是mode & ~umask的结果)
- 必选参数(下面三个必须要有一个)
函数返回值
- 返回最小的空闲的文件描述符,如果失败则返回-1并设置errno,fopen() and creat() return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).
2. close函数
包含头文件
代码语言:javascript复制#include <unistd.h>
函数原型
代码语言:javascript复制int close(int fd);
函数功能
- close() closes a file descriptor, so that it no longer refers to any file and may be reused.
函数参数
- fd :一个文件的文件描述符
函数返回值
- 成功返回0,失败返回-1且设置errno,close() returns zero on success. On error, -1 is returned, and errno is set appropriately.
3. 使用open与close实现touch命令
代码语言:javascript复制/************************************************************
>File Name : mtouch.c
>Author : QQ
>Company : QQ
>Create Time: 2022年05月12日 星期四 19时48分14秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
if(argc < 2)
{
printf("not found file namen");
return -1;
}
int i = 0;
for(i = 1; i < argc; i )
{
int fd = open(argv[i], O_RDONLY | O_CREAT, 0666);
close(fd);
}
return 0;
}
实际上main函数也是有参数和返回值的,只不过我们在平时的学习中可能很少用到,main的返回值是int类型的,main函数的参数在Linux下编程用的还是比较多的。我们在运行一个可执行文件的时候可以在命令行传入参数给argv[],也就是说argv[]是用来存放我们在命令行传入的参数的,而参数argc用于统计参数的个数。不管我们传不传参数, argv[0]默认就是程序运行的路径名。也就是说argc最小为1(命令行不传参),argv[0]是程序运行路径。