进程创建
fork函数, 创建一个子进程. 从已存在的进程中创建一个新进程. 新进程为子进程, 而原进程为父进程
#include <unistd.h>
pid_t fork(void); 创建一个子进程
返回值:父进程返回子进程的PID,子进程返回0,出错返回-1
当一个进程调用fork函数之后, 就有两个二进制代码相同的进程. 而且它们都运行到相同的地方
我们看一段代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;
//创建子进程之前:
printf("fork before: pid is %d\n", getpid());
//创建子进程之后:
if ( (pid=fork()) == -1 )
{
perror("fork failed");
exit(1);
}
printf("fork after: pid is %d, fork() return value: %d\n", getpid(), pid);
sleep(1);
return 0;
}
程序运行结果:
返回值: 父进程返回子进程的PID, 子进程返回0
这里可以看到三行输出, 一行fork before, 两行fork after
进程60181既打印了fork before, 又打印了fork after; 而进程60182只打印了一个fork after
原因是, fork之前父进程独立执行, fork之后, 父子两个进程分别执行. 但是谁先执行完全由CPU调度决定
写时拷贝: 子进程复制了父进程, 一开始与父进程指向同一块物理内存. 因此看起来父子进程完全相同,
但是进程之间具有独立性, 意味着当这块物理内存中的数据发生改变时会重新给子进程开辟物理内存,将数据拷贝过来, 因为子进程也应该有自己的数据
父子进程数据独有, 代码共享. 利用写时拷贝, 可以提高进程的创建效率
vfork函数:
#include <unistd.h>
pid_t vfork(void); 创建一个子进程并阻塞父进程
vfork创建的子进程, 阻塞父进程, 直到子进程exit退出或者程序替换之后, 父进程才会运行
vfork创建子进程效率比较高, 因为vfork创建子进程之后父子进程共用同一个虚拟地址空间, 意味着共用代码段, 数据段. 这样如果父子进程同时运行会造成栈混乱, 因此必须子进程先运行, 父进程阻塞, 直到子进程退出. vfork创建的子进程不能使用return 0;退出,因为会释放进程资源
进程终止
return退出: 例如main函数中的return 0;语句
_exit函数: 系统调用接口, 退出调用进程, 将status返回给父进程
#include <unistd.h>
void _exit(int status);
参数status定义了进程的终止状态,父进程通过wait来获取该值
exit函数: 库函数, 退出调用进程, 将status作为返回值返回给父进程
#include <stdlib.h>
void exit(int status);
下面通过程序来比较不同:
#include<stdio.h>
int main()
{
printf("hello world!");
return 0;
}
运行结果:
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("hello world!");
exit(0);
}
运行结果:
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("hello world!");
_exit(0);
}
运行结果:
我们发现, return退出和exit函数退出效果一样, 执行return n等同于执行exit(n), 因为调用main的运行时函数会将main的返回值当做exit的参数. exit函数和return的区别: return只会在main函数中才会退出进程, 而exit是在任意位置调用都会退出调用进程
而_exit函数没有任何输出, 我们现在将程序修改一下, 在打印内容"hello world!"后加上换行符’\n’
再看看程序运行结果:
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("hello world!\n");
_exit(0);
}
运行结果:
这个时候发现它有输出了!!
我们前面说过换行符\n具有刷新缓冲区的作用, 戳我~传送门
由此可以知道, exit函数和return退出时都会刷新缓冲区;
_exit函数退出的时候直接释放资源, 不刷新缓冲区