From:
http://www.cnblogs.com/jacklu/p/5317406.html
From:
http://www.cnblogs.com/mickole/p/3188321.html
Linux中fork()函数详解 :
http://blog.chinaunix.net/uid-30025978-id-4696748.html
1. 进程控制块(PCB)
在Linux中task_struct结构体即是PCB。PCB是进程的唯一标识,PCB由链表实现(为了动态插入和删除)。
进程创建时,为该进程生成一个PCB;进程终止时,回收PCB。
PCB包含信息:1、进程状态(state);2、进程标识信息(uid、gid);3、定时器(time);4、用户可见寄存器、控制状态寄存器、栈指针等(tss)
每个进程都有一个
非负的
唯一进程ID(PID)。虽然是唯一的,但是PID可以重用,当一个进程终止后,其他进程就可以使用它的PID了。
PID为0的进程为调度进程,该进程是内核的一部分,也称为系统进程;PID为1的进程为init进程,它是一个普通的用户进程,但是以超级用户特权运行;PID为2的进程是页守护进程,负责支持虚拟存储系统的分页操作。
除了PID,每个进程还有一些其他的标识符:
五种进程状态转换如下图所示:
每个进程的task_struct和系统空间堆栈存放位置如下:两个连续的物理页【《Linux内核源代码情景分析》271页】
系统堆栈空间不能动态扩展,在设计内核、驱动程序时要避免函数嵌套太深,同时不宜使用太大太多的局部变量,因为局部变量都是存在堆栈中的。
2. 进程的创建
新进程的创建,首先在内存中为新进程创建一个task_struct结构,然后将父进程的task_struct内容复制其中,再修改部分数据。分配新的内核堆栈、新的PID、再将task_struct 这个node添加到链表中。所谓创建,实际上是“复制”。
子进程刚开始,内核并没有为它分配物理内存,而是以只读的方式共享父进程内存,只有当子进程写时,才复制。即“copy-on-write”。
fork都是由do_fork实现的,do_fork的简化流程如下图:
在Linux系统中,所有的进程都是PID为1的init进程的后代。内核在系统启动的最后阶段启动init进程。该进程读取系统的初始化脚本(initscript)并执行其他的相关程序,最终完成系统启动的整个进程。
Linux提供两个函数去处理进程的创建和执行:fork()和exec()。首先,fork()通过拷贝当前进程创建一个子进程。子进程与父进程的区别仅仅在于PID(每个进程唯一),PPID(父进程的PID)和某些资源和统计量(例如挂起的信号)。exec()函数负责读取可执行文件并将其载入地址空间开始运行。
fork()使用写时拷贝(copy-on-write)页实现。内核在fork进程时不复制整个进程地址空间,让父进程和子进程共享同一个拷贝,当需要写入时,数据才会被复制,使各进程拥有自己的拷贝。在页根本不会被写入的情况下(fork()后立即exec()),fork的实际开销只有复制父进程的页表以及给子进程创建唯一的task_struct。
创建进程的fork()函数实际上最终是调用clone()函数。创建线程和进程的步骤一样,只是最终传给clone()函数的参数不同。比如,通过一个普通的fork来创建进程,相当于:clone(SIGCHLD, 0);创建一个和父进程共享地址空间,文件系统资源,文件描述符和信号处理程序的进程,即一个线程:clone(CLONE_VM | CLONE_FS | CLONE_FILES |CLONE_SIGHAND, 0)。
在内核中创建的内核线程与普通的进程之间还有个主要区别在于:内核线程没有独立的地址空间,它们只能在内核空间运行。
fork和vfork的区别
fork()与vfock()都是创建一个进程,那他们有什么区别呢?总结有以下三点区别:
- fork ():子进程拷贝父进程的数据段,代码段
vfork ( ):子进程与父进程共享数据段 - fork ()父子进程的执行次序不确定
vfork 保证子进程先运行,在调用exec 或exit 之前与父进程数据是共享的,在它调用exec或exit 之后父进程才可能被调度运行。 - vfork ()保证子进程先运行,在她调用exec 或exit 之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
fork函数
fork函数时调用一次,返回两次。在父进程和子进程中各调用一次。
子进程中返回值为0,父进程中返回值为子进程的PID。程序员可以根据返回值的不同让父进程和子进程执行不同的代码。
一个形象的过程:
运行这样一段演示程序:
#include
#include
#include
int main()
{
pid_t pid;
char *message;
int n = 0;
pid = fork();
while(1)
{
if(pid < 0)
{
perror("fork failed
");
exit(1);
}
else if(pid == 0)
{
n--;
printf("child's n is:%d
",n);
}
else
{
n++;
printf("parent's n is:%d
",n);
}
sleep(1);
}
exit(0);
}
运行结果:
可以发现子进程和父进程之间并没有对各自的变量产生影响。
一般来说,fork之后父、子进程执行顺序是不确定的,这取决于内核调度算法。进程之间实现同步需要进行进程通信。
通过for循环创建多个fork
#include
#include
#include
int main(int argc, char *argv[])
{
int n = 5, i; //默认创建5个子进程
if(argc == 2)
{
n = atoi(argv[1]);
}
for(i = 0; i < n; i++) //出口1,父进程专用出口
if(fork() == 0)
break; //出口2,子进程出口,i不自增
if(n == i)
{
sleep(n);
printf("I am parent, pid = %d
", getpid());
}
else
{
sleep(i);
printf("I'm %dth child, pid = %d
", i+1, getpid());
wait(NULL);
}
return 0;
}
代码(多进程):
#include
#include
#include
#include
#include
void sys_err(const char *str)
{
perror(str);
exit(-1);
}
int main(int argc, char *argv[])
{
pid_t pid;
int i, n;
if (argc < 2) {
printf("./a.out numchild
");
exit(-1);
}
n = atoi(argv[1]);
for (i = 0; i < n; i++) {
if ((pid = fork()) < 0)
sys_err("fork");
else if (pid == 0)
break;
}
/* 每个子进程中i的值都是不同的,父进程没有保存子进程的id */
if (pid == 0) {
/* in child */
int m = 0;
//while (m < i) {
while (1) {
printf("i = %d pid=%d
", i, getpid());
sleep(1);
m++;
}
}
if (pid > 0) {
/* in parent */
pid_t cpid;
while ((cpid = wait(NULL)) > 0)
printf("child %d is over
", cpid);
}
return 0;
}
多进程拷贝(实现类似 cp 命令):
#include
#include
#include
#include
#include
#include
#include
#include
#define N 5
struct child_task {
char src[256];
char dest[256];
int addr;
int size;
int num;
};
void sys_err(const char *str)
{
perror(str);
exit(-1);
}
int main(int argc, char *argv[])
{
int nfork, i, task_addr, task_size, total_size, fddest, tmpfd;
pid_t pid;
struct stat sbuf;
int *mem;
if (argc < 3) {
printf("./mycp src dest
");
exit(-1);
}
if (argc == 3)
nfork = N;
else
nfork = atoi(argv[3]);
if (stat(argv[1], &sbuf) < 0)
sys_err("stat");
total_size = sbuf.st_size;
fddest = open(argv[2], O_CREAT|O_RDWR, 0644);
if (fddest < 0)
sys_err("open");
if (lseek(fddest, total_size-1, SEEK_SET) < 0)
sys_err("lseek");
write(fddest, "