系统函数调用
文件描述符
系统调用I/O函数。
文件描述符是非负整数,是文件的标识。
打开现存文件或新建文件时,系统(内核)会返回一个文件描述符。文件描述符用来指定已打开的文件。
在系统调用(文件IO)中,文件描述符对文件起到标识作用,如果要操作文件,就是对文件描述符的操作。
当一个程序运行或者一个进程开启时,系统会自动创建三个文件描述符: - 0 : 标准输入的文件描述符 - 1 : 标准输出的文件描述符 - 2 : 标准错误的文件描述符
利用open打开一个文件时,内核会返回一个文件描述符。
每个进程都有一张文件描述符的表,进程刚被创建时,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符0、1、2记录在表中。
在进程中打开其他文件是,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。
Linux中一个进程最多只能打开NR_OPEN_DEFAULT(即1024)个文件,故当文件不再使用时应及时调用close()函数关闭文件。
文件描述符的复制
dup和dup2是两个非常有用的系统调用,都是用来复制一个文件的描述符,使新的文件描述符也表示旧的文件。
fork
#include <unistd.h>
pid_t fork(void);
成功: 子进程中返回0,父进程中返回子进程id。 失败: 返回-1。
子进程会复制父进程fork()之前的所有内容。 但是fork()之后,父子进程完全独立,所以不管双方怎么改变(堆区、栈区、数据区等),都不会受对方的影响。
但是子进程会继承父进程的一些公有的区域,比如磁盘空间、内核空间。 文件描述符的偏移量就保存在内核空间中,所以父进程改变偏移量,则子进程获取的偏移量是改变之后的。
注意:只要执行一次fork,就会在原有的进程基础上创建一个新的子进程。而且如果fork之后不区分父子进程的代码区,则后面的代码都会执行。
#include <iostream>
#include <unistd.h>
using namespace std;
int main() {
cout << getpid() << endl;
pid_t pid = fork();
cout << "hell " << endl;
cout << pid << endl;
while(1) {}
}
执行结果:
61268
hell
61270
hell
0
#include <iostream>
#include <unistd.h>
using namespace std;
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork fail");
} else if (pid > 0) {
// 父进程代码区
while(1) {
cout << "parent" << endl;
}
} else {
// 子进程代码区
while(1) {
cout << "child" << endl;
}
}
}
vfork()
pid_t vfork()
fork和vfork的区别:
- vfork保证子进程先运行,在它调用exec或exit之后,父进程才可能被调度执行
- vfork和fork一样都创建一个子进程,但它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec或exit,于是也就不访问该地址空间。所以vfork复制后的父子进程会共享同一块空间。