进程和线程的本质

阅读: 评论:0

进程和线程的本质

进程和线程的本质

谁说只有细胞可以分裂的

高中的时候,大家都学过,细胞是可以分裂的,在 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 里面的东西:
![task 里面的东西](.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JsdWVkcmFhbV9wcA==,size_16,color_FFFFFF,t_70

进程要的睡眠

为什么进程需要睡眠?从根本上讲还是 cpu、内存、磁盘、网络处理数据的不同的速度造成的。cpu 处理数据太快了,其他的兄弟们都跟不上节奏。网络模块辛辛苦苦弄了一点数据过来,就让 cpu 秒杀了,等下一波数据来的话,需要等一下。cpu 可以计算里面最珍贵的资源,怎么能让他闲着呢,哪怕 1 us 都不行的,1 us 已经能干好多事情了,所以在 Linux 里面,进程本着大公无私的进程主动挪挪地方,去睡觉吧,资源来了,我在运行,我的运行的上下文,但是进程调度器先替我保存一下我的上下文。

那睡眠和停止(stop)之间有什么区别呢?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 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23