赞
踩
进程是一个运行中的程序,每个进程都有一个进程控制块,英文缩写PCB
,Linux系统中的进程控制块是一个结构体strut task_struct
实现(PCB是进程存在的唯一标志)
数据结构中定义的内容是为后面的管理提供支持的,所以不同的操作系统根据自己的特点又对PCB的内容做了一些调整。
一般情况下,PCB中包含以下内容:
当线程调用fork时,就为子进程创建了整个进程地址空间的副本,子进程和父进程是完全不同的进程,只要两者都没有对内存作出改动,父进程和子进程之间还可以共享内存页的副本。
子进程通过继承整个地址空间的副本、也从父进程那里继承了所有互斥量、读写锁和条件变量的状态、如果父进程包含多个线程、子进程在fork返回之后,如果紧接着不是马上调用exec的话,就需要清理锁状态。
事实上,子进程执行的代码和父进程一模一样,只是fork的返回值不同
注:
执行逻辑:只有当fork执行完成后,子进程才会被复制出来,子进程不会再去fork,并且子进程也不会从头开始执行,而是从返回值处开始执行。
bash就是我们“父进程”的“父进程”
也许有的时候父子进程的执行顺序不一样,是因为父子进程是并发运行
的,执行顺序并不一定固定。
注意并发和并行的区别:
多线程中某个线程调用了fork创建子进程,在子进程中线程的运行情况是怎样的?
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <unistd.h> #include <pthread.h> void *fun(void *arg) { printf("fun start\n"); pid_t pid = fork(); assert(pid != -1); if(pid == 0) { int i = 0; for(; i < 3; ++i) { printf("child: mypid = %d\n", getpid()); sleep(1); } } else { int i = 0; for(; i < 3; ++i) { printf("father: mypid = %d\n", getpid()); sleep(1); } } } int main() { pthread_t id; int res = pthread_create(&id, NULL, fun, NULL); assert(res == 0); int i = 0; for(; i < 5; ++i) { printf("main and mypid = %d\n", getpid()); sleep(1); } pthread_join(id, NULL); exit(0); }
执行结果及结论
示例代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <unistd.h> #include <pthread.h> pthread_mutex_t mutex; void *fun(void *arg) { sleep(1); printf("fun start\n"); // pthread_mutex_lock(&mutex); pid_t pid = fork(); // 调用fork时,互斥锁的状态是加锁状态 assert(pid != -1); // pthread_mutex_unlock(&mutex); // 父子进程都会执行 if(pid == 0) { pthread_mutex_lock(&mutex); int i = 0; for(; i < 3; ++i) { printf("child: mypid = %d\n", getpid()); sleep(1); } pthread_mutex_unlock(&mutex); } else { pthread_mutex_lock(&mutex); int i = 0; for(; i < 3; ++i) { printf("father: mypid = %d\n", getpid()); sleep(1); } pthread_mutex_unlock(&mutex); } } int main() { pthread_mutex_init(&mutex, NULL); pthread_t id; int res = pthread_create(&id, NULL, fun, NULL); assert(res == 0); pthread_mutex_lock(&mutex); int i = 0; for(; i < 5; ++i) { printf("main and mypid = %d\n", getpid()); sleep(1); } pthread_mutex_unlock(&mutex); pthread_join(id, NULL); pthread_mutex_destroy(&mutex); exit(0); }
结论
调用fork时,子进程会复制父进程的锁,(子进程会继承父进程互斥锁的状态)
解决方案
使用互斥锁对fork()调用做保护,使fork在调用过程中的锁是被自己加锁的,线程库提供了一个注册方法pthread_atfork()
注册方法
int pthread_atfork(void (*prepare)();void (*parent),void (*child));
一种推迟或者免除拷贝的技术。
内核fork()时并不复制整个进程地址空间,而是让父子进程共享一个地址空间——>只有在需要写入时,数据才会被复制,从而使各个进程拥有各自的拷贝数据。
也就是说,只有在需要写入的时候才复制资源,在此之前,以只读方式共享。
在进程实体的逻辑页中,假如子进程对0号页和1号页进行了修改,那么就单独按照页表将父子进程的这两个页表单独复制出来,2号页和3号页未被修改,其实是可以共享的,只有需要修改这两个页面时,才单独拷贝出来,相当于将页面的复制时间推迟(这也就是写时拷贝技术的含义),这一点用户也不会感受到。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。