高中的时候,大家都学过,细胞是可以分裂的,在 Linux 系统里面,进程也是可以分裂的。细胞分裂靠的是 DNA 和蛋白质,进程分裂靠的是 task_struct 和 fork().
task_struct {pid , ...mm , -- 内存地址fs , -- 和进程相关的路径files -- 文件资源
}
大家回忆一下高中时候的知识, DNA 是模板,是设计图纸,蛋白质是按照图纸制造新细胞的。同样的 task_struck 描述一个进程的基本信息,像极了细胞的 DNA 。蛋白质根据 DNA 序列,制作出新的蛋白质和DNA,fork 像极了蛋白质。请看下面的图。
进程的分裂
细胞分裂:
进程的分裂还是有点不一样的。
细胞分类以后,就成了两个细胞了。进程分裂以后,除了 pid 相同之外,其他的都是一样的。有人说了,连内存都相同吗?还真是相同。请看下面的图。
talk is cheap show me code :
#include <sched.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int data = 10;
int child_process()
{printf("Child process %d, data %dn",getpid(),data);data = 20;printf("Child process %d, data %dn",getpid(),data);_exit(0);
}
int main(int argc, char* argv[])
{int pid;pid = fork();if(pid==0) {child_process();}else{sleep(1);printf("Parent process %d, data %dn",getpid(), data);exit(0);}
}
cow 是依赖于 mmu(内存管理单元),这是一个硬件单元。执行完 fork 之后,mmu 设置 readonly ,当有进程需要修改内存内容的时候,mmu 发生 page fault ,然后新建一段内存给申请修改操作的进程,然后再对内存进行修改。
如果没有 mmu 的话,我们 linux 只提供了 vfork 了, vfork 在内存是这样的。他没有 cow ,干脆内存就一样吧。所以 vfork 的过程就变成了下面的样子:
talk is cheep ,show me the code :
1 #include <stdio.h>2 #include <sched.h>3 #include <unistd.h>45 int data = 10;67 int child_process()8 {9 printf("Child process %d, data %dn",getpid(),data);10 data = 20;11 printf("Child process %d, data %dn",getpid(),data);12 _exit(0);13 }1415 int main(int argc, char* argv[])16 {17 if(vfork()==0) {18 child_process();19 }20 else{21 sleep(1);22 printf("Parent process %d, data %dn",getpid(), data);23 }24 }
运行这点代码的结果是
Child process 1345, data 10
Child process 1345, data 20
Parent process 1344, data 20
由结果可以看到 子进程和父进程中的 data 是不一样的。
这里有一个问题,既然进程和线程之间的区别在于是否共享内存,里面 task_struct 是一样的。按照上面的说法,进程里面的线程 pid 是不一样的,为什么我们 top 看到的 pid 都是一样的呢?看看下面的 code 吧!
1 #include <stdio.h>2 #include <pthread.h>3 #include <stdio.h>4 #include <linux/unistd.h>5 #include <sys/syscall.h>67 static pid_t gettid( void )8 {9 return syscall(__NR_gettid);10 }1112 static void *thread_fun(void *param)13 {14 printf("thread pid:%d, tid:%d pthread_self:%lun", getpid(), gettid(),pthread_self());15 while(1);16 return NULL;17 }1819 int main(void)20 {21 pthread_t tid1, tid2;22 int ret;2324 printf("thread pid:%d, tid:%d pthread_self:%lun", getpid(), gettid(),pthread_self());25 /*new a thread and call thread_fun*/26 ret = pthread_create(&tid1, NULL, thread_fun, NULL);27 if (ret == -1) {28 perror("cannot create new thread");29 return -1;30 }31 /*new another thread and call thread_fun*/32 ret = pthread_create(&tid2, NULL, thread_fun, NULL);33 if (ret == -1) {34 perror("cannot create new thread");35 return -1;36 }37 /*main thread use pthread_join to wait a sub_thread */38 if (pthread_join(tid1, NULL) != 0) {39 perror("call pthread_join function fail");40 return -1;41 }42 /*at the same time . main thread use pthread_join to wait another sub_thread */43 if (pthread_join(tid2, NULL) != 0) {44 perror("call pthread_join function fail");45 return -1;46 }4748 return 0;49 }
结果是:
thread pid:1554, tid:1554 pthread_self:139970410002240
thread pid:1554, tid:1555 pthread_self:139970401605376
thread pid:1554, tid:1556 pthread_self:139970393212672
pstree 看一下:
看到进程树,我想到一个问题,ps 或者 top 进去里面的 pid 是父进程的进程号还是子进程或者子线程的 pid。这就是引出了一个问题——GTID(global task id) ,GTID 就是一个总代理,他的后面是一个一个进程线程ID,我用下面的图来说明我的想法:
这就是 GTID 了,明白了很简单。
在上面的图中,我们可看到一个树状的进程关系实例。假设,两个 parent 和 Child 进程死掉以后,那子进程改如何该挂到谁的底下。
子进程有两个选择,一个是找 init 线程,一个是 subreaper 线程。
talk is sheep show me the code .
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
13 if (pid==-1) {
14 perror("Cannot create new process");
15 exit(1);
16 } else if (pid==0) {
17 printf("child process id: %ldn", (long) getpid());
18 pause();
19 _exit(0);
20 } else {
21 printf("parent process id: %ldn", (long) getpid());
22 wait_pid=waitpid(pid, &status, WUNTRACED | WCONTINUED);
23 if (wait_pid == -1) {
24 perror("cannot using waitpid function");
25 exit(1);
26 }
27
28 if(WIFSIGNALED(status))
29 printf("child process is killed by signal %dn", WTERMSIG(status));
30
31 exit(0);
32 }
33 }
上面的程序跑起来后,会 fork 出一个子进程。我们 kill 掉父进程后,看看情况。如下:
上图中可以看到 18060 是父进程,18064 是 18060 的子进程,现在再来看一下 pstree 的结果:
kill 掉父进程的如下:
这力在认识一下,Linux 的目录/proc
目录里面的好东东。
执行ls /proc | egrep '^d'
,会发现在这个目录下面有许多以数字为名字的文件,这些可不是普通的文件,这些文件正式 Linux 内核提供给我们的线程的信息。
例如,我们执行之前写的多进程的例子。
当我们执行./a.out
后,在/proc
的目录地下会生成三个文件:
这三个文件就对应了这三个进程的进程号。
其中每个文件的 task 子文件夹中,我们都可以找到下面的内容:
还有更有趣的东西,你会发现这三个“进程”是一个爹。
再来看看 pstree 里面是如何显示的:
在 pstree 的手册中,我们可以看到a.out───2*[{a.out}]
。其中 [{}] 里面包含的是父进程的两个子线程,而且两个子线程的名字是一样的。在代码里面我看到使用了 create_thread 函数新建的子线程。在这里可以得出结论,父进程和子矜持之间的内存是共享的,连 proc 里面的东西都是一样的。
pstree 的手册里面,还说了父进程下面的名字重复的子进程使用[]
表示的。请看下面的代码:
1 main()
2 {
3 fork();
4 printf("hellon");
5 fork();
6 printf("hellon");
7 while(1);
8 }
pstree 以后:
从上面图里面很容易看出下面进程之间的父子关系。
1540--|----1541|-----1542
在来看看 proc 里面的东西:
之间有什么区别呢?stop 是其他的进程给进程一个信号,你先暂停一下。“睡眠”是说,我的资源没来了,让其他进程先用 cpu 吧。睡眠是进程主动让出 cpu。停止是赶鸭子下架,强行的把进程从 cup 上拉下来。
深睡眠是是雷打不动那种,谁来叫都不行,让我醒来也可以,条件只要一个我等的资源来了就可以了
浅睡眠是雷一打就动,一有其唤醒的信号进来,我就醒了。
Linux 系统中,存在一个创世进程——进程中的德古拉伯爵。就是 0 号进程。0 号进程的任务两个任务。下面是一个:
0号进程->1号内核进程->1号用户进程(init进程)->getty进程->shell进程
ok,简单的说就是研究“生”,繁殖后代。
另外一个任务是蜕化成 IDLE 进程,当所有的进程都睡了,就该他上场了。这是调度算法决定的,这个时候,cpu 进入低功耗状态。
本文发布于:2024-01-29 07:24:02,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170648424513654.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |