LINUX下的进程与线程

1.系统的启动运行进程
  • 当用户进程init开始运行,就开始扮演用户进程的祖先角色,永远不会被终止。
  • 所以:计算机上的所有进程都是有上下亲属关系的,他们组成一个庞大的家族树。

※ 观察linux下的进程间父子关系:

  • pstree
    以树状结构方式列出系统中正在运行的各进程间的父子关系。
  • ps ax -o pid,ppid,command
    在这里插入图片描述
2. 基本进程编程

在这里插入图片描述

(1)新建进程——fork
  • pid_t fork(void);
  • 一次克隆,两个返回值
  • 两个执行线路
//示例1:执行一次fork语句,父亲睡眠2秒
[flora@localhost ~]$ vi fork_1.c
[flora@localhost ~]$ gcc -o myfork_1 fork_1.c
[flora@localhost ~]$ cat fork_1.c 
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void){
    pid_t pid;
    char *message;
    int x; 
    pid = fork(); 
        if (pid < 0)
         {   perror("fork failed");
             exit(1);    }
        if (pid == 0) 
         {   message = "This is the child\n";
             x = 0;  } 
        else 
         {   message = "This is the parent\n";              
             x = 10;
             sleep(2);     }   //注意
        printf("%s I'm %d, x=%d,my father is:%d\n",message,getpid(),x,getppid());
        return 0;
} 
[flora@localhost ~]$ ./myfork_1 
This is the child
 I'm 3928, x=0,my father is:3927
This is the parent
 I'm 3927, x=10,my father is:2725

上例运行结果:先输出孩子语句,等待两秒后输出父亲语句。

//示例2:执行一次fork语句,孩子睡眠2秒
[flora@localhost ~]$ vi fork_2.c
[flora@localhost ~]$ cat fork_2.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void){
    pid_t pid;
    char *message;
    int x; 
    pid = fork();
    if (pid < 0)
       {   perror("fork failed");
           exit(1);    }
    if (pid == 0) 
       {   message = "This is the child\n";             
           x = 0;   
           sleep(2);     }    //注意
    else 
       {   message = "This is the parent\n";
           x = 10;
                         }
      printf("%s I'm %d, x=%d,my father is:%d\n",message,getpid(),x,getppid());      return 0;
}
[flora@localhost ~]$ gcc -o myfork_2 fork_2.c 
[flora@localhost ~]$ ./myfork_2
This is the parent
 I'm 3168, x=10,my father is:3084
[flora@localhost ~]$ This is the child
 I'm 3169, x=0,my father is:1
ps
   PID TTY          TIME CMD
  2721 pts/0    00:00:00 bash
  2893 pts/0    00:00:00 bash
  3084 pts/0    00:00:00 bash
  3179 pts/0    00:00:00 ps

上例结果分析:先输出父亲语句,父亲语句执行完后,bash获得了cpu,把命令行输出了一下,然后孩子睡两秒后输出语句。
注意:可以看到父亲的父亲是bash,而孩子的父亲是1(因为父亲执行完后就结束了,孩子睡完再执行时,孩子的父亲就是祖先进程了,由祖先进程回收进程)

注意:当循环语句中含fork语句时,循环n次,则有2^n个进程。
但是当子进程中含有while语句时情况则不同,如下图:
在这里插入图片描述
产生的进程数:循环n次,则有n个子进程,再加一个父进程(因为由于fork语句产生的新子进程中有while语句,所以当执行子进程时就会一直while下去,后续就不再执行fork语句循环产生新的子进程了)

(2)执行新工作——exec
  • int execve()是最根本的系统调用
  • 需要路径、命令行参数、和环境变量三种参数
    在这里插入图片描述

在这里插入图片描述

  • p:利用PATH环境变量查找可执行的文件;
  • l:希望接收以逗号分隔的形式传递参数列表,列表以NULL指针作为结束标志;
  • v:希望以字符串数组指针( NULL结尾)的形式传递命令行参数;
  • e:传递指定参数envp,允许改变子进程的环境,后缀没有e时使用当前的程序环境

注意:只有execve是系统调用;其他函数调用最终都由execve完成工作。无论是哪个exec类函数,都是将要执行程序的路径、命令行参数、和环境变量3个参数传递给execve

示例:

[flora@localhost ~]$ vi forkexec_1.c
[flora@localhost ~]$ cat forkexec_1.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void){
    pid_t a;
    a = fork();
    if (a < 0)  {
        perror("fork failed");
        exit(1);  
    }
    if (a == 0) {
        execlp ("ps" ,"ps",NULL);   
        execlp ("ls" ,"ls","-al","/etc/passwd ",NULL); 
        return 1;
    } 
    else    {
        wait(NULL);
        printf("father exit\n");
    }
    return 0;
}
[flora@localhost ~]$ gcc -o myforkexec_1 forkexec_1.c 
[flora@localhost ~]$ ./myforkexec_1 
   PID TTY          TIME CMD
  2721 pts/0    00:00:00 bash
  2893 pts/0    00:00:00 bash
  3084 pts/0    00:00:00 bash
  6591 pts/0    00:00:00 myforkexec_1
  6592 pts/0    00:00:00 ps
father exit

上例结果分析:因为父进程有wait语句,相当于阻塞态,所以cpu会先给孩子进程,执行第一条execlp语句,但是当执行完这条语句后,已经覆盖掉了孩子进程中的代码,所以不会执行后续的指令了,当孩子进程执行完后,系统会触发刚才的父亲进程,父亲进程被唤醒,输出它的语句。

注意:

  • exec函数执行成功会装入新代码,原来的代码在其进程空间中已不存在。
  • 只有exec调用失败返回-1,其后的代码才有可能得到执行。

拓展:若修改子进程代码:在开始时加入输出语句

if (a == 0) {
        printf("I am child\n");
        execlp ("ps" ,"ps",NULL);   
        execlp ("ls" ,"ls","-al","/etc/passwd ",NULL); 
        return 1;
    } 

则结果如下图:
在这里插入图片描述

(3)进程的消亡——exit
  • void exit(int status);
  • 需要路径、命令行参数、和环境变量三种参数

注意:程序执行结束或调用exit后并不是马上消失,而是变为僵死状态——放弃了几乎所有内存空间,不再被调度,但保留有PCB信息供wait收集。

示例:注意要想看子进程的僵死态,必须让父进程睡眠一段时间,否则父进程结束后,就只能由祖先进程回收子进程,就看不到僵死态了

[flora@localhost ~]$ vi fork_1.c
[flora@localhost ~]$ cat fork_1.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void){
    pid_t pid;
    char *message;
    int x; 
    pid = fork();
        if (pid < 0)
         {   perror("fork failed");
             exit(1);    }
        if (pid == 0) 
         {   message = "This is the child\n";
             x = 0;  } 
        else 
         {   message = "This is the parent\n";              
             x = 10;
             sleep(20);     }
        printf("%s I'm %d, x=%d,my father is:%d\n",message,getpid(),x,getppid());
        return 0;
} 
[flora@localhost ~]$ gcc fork_1.c
[flora@localhost ~]$ ./a.out&    //注意:&使进程在后台执行
[1] 7202
[flora@localhost ~]$ This is the child
 I'm 7203, x=0,my father is:7202
ps -x|grep a.out    //此时父亲在睡眠,此契机下,用ps命令观察子进程死亡前状态
  7202 pts/0    S      0:00 ./a.out            //睡眠态
  7203 pts/0    Z      0:00 [a.out] <defunct>  //僵死态
  7205 pts/0    R+     0:00 grep --color=auto a.out
[flora@localhost ~]$ This is the parent   //父亲睡眠后返回,打印其输出
 I'm 7202, x=10,my father is:3084
 
 [1]+  完成                  ./a.out
(4)进程间的等待——wait
  • pid_t wait(int *status)
  • 使调用它的进程等待,进入阻塞状态
  • 子进程死亡会唤醒阻塞的父进程(但注意:一个孩子只能唤醒一次父亲,如果有多个孩子的话,则需要加多个wait语句)

示例:
在这里插入图片描述

[flora@localhost ~]$ vi forkwait_cp.c
[flora@localhost ~]$ cat forkwait_cp.c
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(){
    pid_t pc1,pc2,pw1,pw2;
    pc1=fork();
    pc2=fork();
    if (pc1>0&&pc2>0) {     //*父进程
        pw1=wait(NULL);
        printf("***Catch a dead child process with pid: %d\n",pw1);
        pw2=wait(NULL);
        printf("***Catch a dead child process with pid: %d\n",pw2);
        printf("***I'M %d,THE MAIN PROCESS LEAVE!\n",getpid()); \
    }
    if (pc1==0&&pc2>0) {        //*子进程1
        printf("I'M the first child PID:%d,my father is:%d\n",getpid(),getppid());
        sleep(10);               
    }
    if (pc1>0&&pc2==0)          //*子进程2
        printf("I'M the 2nd child PID:%d,my father is:%d,i don't sleep.\n",getpid(),getppid());
    if (pc1==0&&pc2==0)         //*孙进程
        printf("I'M grandson PID:%d,my father is:%d,no one is waiting for – me.\n",getpid(),getppid());
    
    exit(0);    
}   
[flora@localhost ~]$ gcc forkwait_cp.c -o myforkwait_cp
[flora@localhost ~]$ ./myforkwait_cp
I'M the 2nd child PID:7852,my father is:7850,i don't sleep.
***Catch a dead child process with pid: 7852
I'M the first child PID:7851,my father is:7850
I'M grandson PID:7853,my father is:7851,no one is waiting for – me.
***Catch a dead child process with pid: 7851
***I'M 7850,THE MAIN PROCESS LEAVE!

上例结果分析:
可以看出由于父进程有wait语句,所以cpu会先给子进程,而子进程1有睡眠时间,因此子进程2会先输出执行,结束后会唤醒一次父进程,输出对应的语句;然后由于父进程还有一个wait语句,所以子进程1和孙进程会先后执行输出对应的语句,停顿10s后,子进程1结束再次唤醒父进程,输出对应语句,最后再输出自己的语句,而孙进程则由祖先进程回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值