51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 1676|回复: 0
打印 上一主题 下一主题

面试官,您确定要让我展开讲进程吗?

[复制链接]
  • TA的每日心情
    无聊
    11 小时前
  • 签到天数: 938 天

    连续签到: 5 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2021-10-19 16:50:53 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    大家好呀,我是大白!准备了好久,我的 Linux 系列文章终于和大家见面了。最近为了筹备 Linux 的文章,以程序员的视角看城市系列都没时间写了,最近不光读者催我,一些喜欢我城市系列文章的号主也在催我,哈哈。现在我的第一篇写 Linux 的文章终于写出来了,第二篇、第三篇会越来越快,质量也会越来越高,希望大家多多支持我呀!
      面试中经常会被问到进程和线程的区别,你给面试官回答一个进程里包含许多线程他又不太高兴,嫌你答的太少了,要挂你。这个系列我们就以 Linux 中的进程和线程为例,好好的拆开讲一讲里面的细节。今天这篇文章先展开讲一讲 Linux 的进程是如何创建的。这个系列还会不断更新,欢迎持续关注呀!
      Linux的进程是怎样创建的
      Linux系统创建进程都是由已存在的进程创建的(除了0号进程),被创建的进程叫做子进程,创建子进程的进程就做父进程。这句话是不是有点熟悉,没错,Linux进程串起来也是一颗树的结构。就像下面这样:

    在Linux中,为了创建一个子进程,父进程用系统调用fork来创建子进程。fork()其实就是把父进程复制了一份(子进程有自己的特性,比如标识、状态、数据空间等;子进程和父进程共同使用程序代码、共用时间片等)。
      可以看下面这段代码:
    1. #include<stdio.h>
    2.   #include<unistd.h>
    3.   
    4.   int main()
    5.   {
    6.       int p_num = 0;
    7.       int c_num = 0;
    8.       int pid = fork();
    9.       if(pid == 0) //返回的pid为0为子进程
    10.       {
    11.           c_num++;
    12.       }
    13.       else
    14.       {
    15.           p_num++; //返回的pid大于0为父进程
    16.       }
    17.       printf("p_num=%d, c_num=%d\n",p_num,c_num);
    18.       printf("pid=%d\n",pid);
    19.       return 0;
    20.   }
    21.   //运行结果如下所示
    22.   p_num=1, c_num=0
    23.   pid=36101
    24.   p_num=0, c_num=1
    25.   pid=0
    复制代码
    大家看,代码中调用了fork以后,之后的程序被执行了两遍。子进程和父进程各自的变量互相没有受到干扰。不过子进程和父进程执行的是相同的代码,子进程和父进程资源占用情况如下图所示:


    大家可以看出,通过fork后,子进程并没有和父进程独立开,用的是相同的代码。另外还有一个问题时,这个时候子进程的时间片是和父进程一分为二来共享的。这样我创建子进程还有什么意义?为了彻底将父进程和子进程分离开来,就要用到一个系统调用 execv()。
      看下面这段代码:
    1. //process.c
    2.   #include<stdio.h>
    3.   #include<unistd.h>
    4.   
    5.   int main()
    6.   {
    7.       int pid = fork();
    8.       if(pid == 0)
    9.       {
    10.           execv("./test.o",NULL);  //test.o是一个经过编译的c语言文件,这里记得要放test.o的绝对路径
    11.       }
    12.       printf("This is parent process\n");
    13.       return 0;
    14.   }
    15.   
    16.   //test.c
    17.   #include<stdio.h>
    18.   int main()
    19.   {
    20.       printf("This is child process");
    21.       return 0;
    22.   }
    23.   
    24.   //运行结果如下所示
    25.   This is parent process
    26.   This is child process
    复制代码
     通过上面的代码可以看出,从系统调用 execv() 后,子进程直接走自己的代码了,没有像前一段代码一样把后面的代码执行了两次。通过调用 execv(),子进程和父进程就基本分离开了。
      结合系统继续看Linux的进程树是什么样的
      好了,通过上面的介绍,大家应该对进程是怎么创建的有一定的了解。想继续学习的我们来接着上强度。
      我们在 Linux 系统上通过 ps - ef 命令查看系统目前的进程:
    1.  /[root[url=home.php?mod=space&uid=152075]@localhost[/url] lucas]# ps -ef
    2.   UID          PID    PPID  C STIME TTY          TIME CMD
    3.   root           1       0  3 21:41 ?        00:02:38 /usr/lib/systemd/systemd --s
    4.   root           2       0  0 21:41 ?        00:00:07 [kthreadd]
    5.   root           3       2  0 21:41 ?        00:00:00 [rcu_gp]
    6.   root           4       2  0 21:41 ?        00:00:00 [rcu_par_gp]
    7.   ...
    8.   rtkit       1151       1  0 21:41 ?        00:00:14 /usr/libexec/rtkit-daemon
    9.   root        1152       1  0 21:41 ?        00:00:00 /usr/sbin/ModemManager
    10.   avahi       1155       1  0 21:41 ?        00:00:06 avahi-daemon: running [linux
    11.   root        1159       1  0 21:41 ?        00:00:02 /usr/lib/systemd/systemd-mac
    复制代码
    我来解释上表是什么意思。
      首先,每一个进程都要所属一个用户,UID 就是用户的标识符(通过 root 用户创建的进程 UID 就是 root,如果我自己创建的话就应该是我的用户名,比如我的名字 "dabai")。
      其次每一个进程都要有一个 ID 来表示这个进程,PID 就表示的是当前进程的 id。
      最后,上文提到除了 0 号进程,每一个进程都是由他的父进程创建的,PPID 就表示当前进程的父进程 id。
      通过 0 号进程创建 1 号进程和 2 号进程,然后通过 1 号进程去创建用户态进程,再通过 2 号进程创建内核态进程,就生成了 Linux 进程树。

    「什么是0号进程、1号进程以及2号进程?」。
      0号进程:在内核初始化的过程中,会先通过指令 struct task_struct init_task = INIT_TASK(init_task) 创建 0 号进程。这是唯一一个没有通过 fork 或者 kernel_thread 产生的进程。是进程列表的第一个。但是这个进程不是实际意义上的进程,类似与链表头。所以虽然 0 号进程是在内核态创建的,但不能说 0 号进程是内核态的第一个进程,反而要说 2 号进程是内核态的第一个进程。
      1号进程:通过调用指令 kernel_thread(kernel_init, NULL, CLONE_FS) 从内核态切换到用户态来创建的,1号进程是所有用户态的祖先。
      2号进程:通过调用指令 kernel_thread(kthreadd, NULL, ClONE_FS | CLONE_FILES) 来创建,2号进程负责所有内核态的进程的调度和管理,是内核态所有进程的祖先。(注意,内核态不区分线程和进程,所以说进程和线程都可以,都是任务)
      「为什么要先创建 0 号进程,而不直接创建 1 号进程?」
      现在对于为什么要先创建 0 号进程而不直接创建1号和2号进程有许多讨论。我认为...算了,我不认为了,一展开讲这篇文章又收不了尾了,以后可以专门写一篇文章来论述这里。简单来说就是Linux 的第一个进程不适合是一个真进程,需要一个没有数据之类东西的假进程。
      「为什么要区分用户态和内核态?」
      因为有了多个进程,对于关键资源来说,就会产生争用以及误操作破坏资源等情况。这时就需要对资源的访问权限进行一定的限制。x86 提供了分层的权限机制,内核态具有最高的访问权限,而用户态访问核心资源时必须要切换到内核态才可以访问。






    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-4-26 20:24 , Processed in 0.067474 second(s), 24 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表