任督二脉之进程管理(2)
一、第二次课大纲
1.fork、vfork、clone
2.写时拷贝技术
3.Linux线程的实现本质
4.进程0和进程1
5.进程的睡眠和等待队列
6.孤儿进程的托孤,SUBREAPER
1.fork、vfork、Copy-on-Write例子
2.life-period例子,实验体会托孤
3.pthread_create例子,strace它
4.彻底看懂等待队列的案例
二、fork
1)fork创建一个进程。p1是一个task_struct,p2也是一个task_struct,调度器只通过task_struct来调度进程。在p1中fork后新进程p2,直接完全对拷给p2的task_struct,两者完全一样(当然pid不同),资源一样。只要有修改就不一样了,资源分裂。比如fs,fd等。chdir函数修改cwd。
2)但是内存资源mm不好分裂。采用写时拷贝技术(COW)
例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <sched.h> 2 3 #include <unistd.h> 4 5 #include <stdio.h> 6 7 #include <stdlib.h> 8 9 10 11 int data = 10; 12 13 14 15 int child_process() 16 17 { 18 19 printf("Child process %d, data %d\n",getpid(),data); 20 21 data = 20; 22 23 printf("Child process %d, data %d\n",getpid(),data); 24 25 _exit(0); 26 27 } 28 29 30 31 int main(int argc, char* argv[]) 32 33 { 34 35 int pid; 36 37 pid = fork(); 38 39 40 41 if(pid==0) { 42 43 child_process(); 44 45 } 46 47 else{ 48 49 sleep(1); 50 51 printf("Parent process %d, data %d\n",getpid(), data); 52 53 exit(0); 54 55 } 56 57 }
运行结果:
下面是COW详细过程:fork后父子进程的页表完全一样,但是只有RD_ONLY,其中一个进程P2修改数据,那么就申请一个新的物理页面,把原页面数据拷贝到新的物理页面,再修改P2进程页表。
3)从上面可以看出,COW技术依赖于MMU(硬件),那么没有MMU的系统,比如uclinux,不能执行COW,没有fork函数,只有vfork。vfork会阻塞父进程(子进程调用_exit,exec之一才返回)。所以vfork后P1和P2的mm指针指向同一个地址。
vfork后P1和P2的mm指针指向同一个地址。
例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <sched.h> 2 3 #include <unistd.h> 4 5 #include <stdio.h> 6 7 #include <stdlib.h> 8 9 10 11 int data = 10; 12 13 14 15 int child_process() 16 17 { 18 19 printf("Child process %d, data %d\n",getpid(),data); 20 21 data = 20; 22 23 printf("Child process %d, data %d\n",getpid(),data); 24 25 _exit(0); 26 27 } 28 29 30 31 int main(int argc, char* argv[]) 32 33 { 34 35 int pid; 36 37 pid = vfork(); 38 39 40 41 if(pid==0) { 42 43 child_process(); 44 45 } 46 47 else{ 48 49 sleep(1); 50 51 printf("Parent process %d, data %d\n",getpid(), data); 52 53 exit(0); 54 55 } 56 57 }
运行结果:
那么可以放大,把各种资源全部指向同一个地址,资源全部共享。也即pthread_create(其调用了clone函数,再共享其他资源),这就是轻量级进程,即线程。
4)也可以只共享部分资源,clone函数的参数指定。既不是进程也不是线程。
5)PID和TGID
以上fork,vfork,clone都会产生新的task_struct,有独立的pid。POSIX要求一个进程里面有多个线程,必须对外表现为一个整体,getpid时是同一个值。linux就增加一个TGID=P1。
例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <stdio.h> 4 #include <linux/unistd.h> 5 #include <sys/syscall.h> 6 7 static pid_t gettid( void ) 8 { 9 10 return syscall(__NR_gettid); 11 12 } 13 14 static void *thread_fun(void *param) 15 { 16 17 printf("thread pid:%d, tid:%d pthread_self:%lu\n", getpid(), gettid(),pthread_self()); 18 19 while(1); 20 21 return NULL; 22 } 23 24 int main(void) 25 { 26 pthread_t tid1, tid2; 27 int ret; 28 29 printf("thread pid:%d, tid:%d pthread_self:%lu\n", getpid(), gettid(),pthread_self()); 30 /*getpid():TGID, gettid():内核空间的pid,pthread_self(): 用户空间thread库里的id*/ 31 32 33 ret = pthread_create(&tid1, NULL, thread_fun, NULL); 34 35 if (ret == -1) { 36 37 perror("cannot create new thread"); 38 39 return -1; 40 41 } 42 43 ret = pthread_create(&tid2, NULL, thread_fun, NULL); 44 if (ret == -1) { 45 perror("cannot create new thread"); 46 return -1; 47 } 48 49 if (pthread_join(tid1, NULL) != 0) { 50 perror("call pthread_join function fail"); 51 return -1; 52 } 53 54 if (pthread_join(tid2, NULL) != 0) { 55 perror("call pthread_join function fail"); 56 return -1; 57 } 58 59 return 0; 60 }
top命令看到的多线程进程是一个整体,top -H就是线程视角,也可以运行top后按H转换视角。
pstree:
进程视角:
线程视角:
三、subreaper和托孤
subreaper:通过系统调用设置自己为subreaper,pstree里面显示为init的为subreaper。
在进程退出后会在父子关系树向上找sbureaper,把子进程托孤给它,找不到sbureaper看托孤给init进程。
例子,托孤:先pstree观察父子进程在树形结构中的位置,然后把父进程杀掉,pstree观察子进程在树形结构中的位置变化。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <sys/wait.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 6 int main(void) 7 { 8 pid_t pid,wait_pid; 9 int status; 10 11 pid = fork(); 12 if (pid==-1) { 13 perror("Cannot create new process"); 14 exit(1); 15 } else if (pid==0) { 16 printf("child process id: %ld\n", (long) getpid()); 17 pause(); 18 _exit(0); 19 } else { 20 printf("parent process id: %ld\n", (long) getpid()); 21 wait_pid=waitpid(pid, &status, WUNTRACED | WCONTINUED); 22 if (wait_pid == -1) { 23 24 perror("cannot using waitpid function"); 25 exit(1); 26 27 } 28 29 if(WIFSIGNALED(status)) 30 printf("child process is killed by signal %d\n", WTERMSIG(status)); 31 32 exit(0); 33 } 34 }
三、睡眠:深度睡眠VS浅度睡眠
睡眠的实现:关键--等待队列(类似设计模式中的观察者模式),等待资源的进程加入等待队列,资源到来后唤醒等待队列不会唤醒单个进程。看globalfifo代码:https://github.com/21cnbao/process-courses/blob/master/day2/globalfifo.c
四、进程0和1
1号进程的父进程pid=0。但是pstree里面看不到0号进程,实际上0号进程创建完init进程后就退化为idle进程,也就是其他进程都不用cpu的情况下才运行,也即调度优先级最低,进入睡眠状态WFI,省电。设计的好处:每个进入进入睡眠之前就查一下是不是最后一个进程,是的话就置为WFI低功耗,这样就耦合到了每个进程里面;idle的设计就没有耦合了。
1 sh@zsh-vm64:~/vmware-tools-distrib$ cat /proc/1/status 2 Name: systemd 3 State: S (sleeping) 4 Tgid: 1 5 Ngid: 0 6 Pid: 1 7 PPid: 0 8 TracerPid: 0 9 Uid: 0 0 0 0 10 Gid: 0 0 0 0 11 FDSize: 256 12 Groups: 13 NStgid: 1 14 NSpid: 1 15 NSpgid: 1 16 NSsid: 1
五、问答
1、进程0是天上掉下来的,是从boot跑到linux。???????也是有task_struct的。
2、vfork阻塞父进程,子进程一般是创建后执行exec。
3、多线程的进程,/proc/tgid/task/pid。
4、MMU原理,数据结构
http://ytliu.info/blog/2014/11/24/shi-shang-zui-xiang-xi-de-kvm-mmu-pagejie-gou-he-yong-fa-jie-xi/