Linux:进程创建详解

进程创建

现在我们已经知道进程的概念以及怎样创建一个进程,接下来我们来详细探索一下进程创建的细节。

1.fork函数

在Linux中fork函数可以从已存在的进程中创建一个新进程,新进程为子进程,原进程为父进程

#include <unistd.h> //头文件

pid_t = fork(void);
//返回值有三种情况
//pid_t = 0 表示子进程
//pid_t > 0 表示父进程
//pid_t < 0 表示创建失败

进程调用fork函数,当控制转移到内核中的fork代码后,内核需要做的事为:
1.分配新的内存块和内核数据结构给子进程
2.将父进程部分数据结构内容拷贝给子进程
3.添加子进程到系统进程列表中
4.fork返回。开始调度器调度
通过调用fork创建子进程的主要思想是通过复制父进程创建一个子进程(复制了父进程pcb中的数据),代码共享但是数据独有。
在这里插入图片描述

int main( void )
{
    
    
 pid_t pid;
 printf("Before: pid is %d\n", getpid());
 if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
 printf("After:pid is %d, fork return %d\n", getpid(), pid);
 sleep(1);
 return 0;
}

在这里插入图片描述这里看到了三行输出,一行before,两行after。进程3255先打印before消息,然后它有打印after。另一个after消息有3256打印的。注意到进程3256没有打印before,原因如下图所示在这里插入图片描述

写时拷贝

写时拷贝是一种可以推迟甚至免除拷贝数据的技术,内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。写时拷贝,只有在写入的时候,数据才会被复制,从而使各个进程拥有自己的的拷贝,也就是说,资源的复制只在需要写入的时候才进行,在此之前,只是以只读的方式共享。简单来说就是:通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
在这里插入图片描述

调用失败的原因

(1).系统中有太多的进程
(2).实际用户的进程数超过了限制

2.vfork函数

vfork()和fork()的功能一样,不同的是vfork()不拷贝父进程的页表项。通过这种方法创建进程时,子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,也就是说只有当子进程退出后,父进程才可以继续执行,子进程不能向地址空间写入。使用vfork的好处是不用拷贝父进程的页表项。(注:随着写时拷贝技术的出现,vfork一般不再使用)

进程终止

就是进程的退出一般来说有正常退出和异常退出两种情况。
正常退出:进程运行到return或者exit的地方退出也就是在我们在我们让它退出的地方退出
异常退出:没有完成任务,程序运行到一半就因为某些原因退出了。

正常退出的三种方法

(1)_exit函数
在任意位置调用void exit(int status);可以在程序的任意位置退出一个进程

#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值

说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255
(2).exit函数
在任意位置调用void _exit(int status);可以在程序的任意位置退出一个进程

#include <unistd.h>
void exit(int status);

(3).return退出
main函数中的return; (普通函数中的return只能退出函数,不能退出进程),return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

_exit和exit的区别

在任意位置调用void exit(int status/void _exit(int status),都可以可以在程序的任意位置退出一个进程但是前者退出进程时会刷新缓冲区,将缓冲区中的数据写入数据,而后者退出进程时,直接释放资源,不会刷新缓冲区;exit是库函数,_exit是系统调用接口

猜你喜欢

转载自blog.csdn.net/qq_43825377/article/details/113817840