• 进程(第三章)


    进程是对映像的执行

    系统资源包括内存空间、I/O设备、CPU

    PCB进程控制块,PROC结构体
    简易的PROC:

    typedef struct proc{
        struct proc *next; // next proc pointer
        int *ksp; // saved sp: at byte offset 4
        int pid; // process ID
        int ppid; // parent process pid
        int status; // PROC status=FREE|READY, etc.
        int priority; // scheduling priority
        int kstack[1024]; // process execution stack
    }PROC;
    
    • ksp 保存堆栈指针,以便进程恢复。当进程放弃CPU时,会将上下文保存在堆栈中。
    • pdi 进程id编号
    • ppid 父进程id编号
    • status 进程当前状态
    • priority 进程调度优先级
    • kstack[1024]进程执行时的堆栈

    睡眠模式,应该就是阻塞
    进程被阻塞之后,放弃CPU,转交给进程调度,自己等待资源满足后被唤醒

    唤醒操作
    被唤醒的进程只是进入就绪状态,进入就绪队列,是否运行还要看进程调度

    fork函数(最有收获)

    返回值:子进程中返回0 ,父进程中返回子进程id,错误返回-1

    img

    fork()之前的代码,子进程无法执行。
    fork()之后的代码,子进程有,并且可以执行。
    既然如此,那子进程和父进程在fork()之后的行为,岂不是一模一样?
    没错,相当于一份代码,控制两个不同进程,进行不同的行为,所以需要用到分支语句,再加上fork()函数在两个进程中不同的返回值,让父子进程进入相同代码的不同分支中执行。

    那如果要创建多个子进程呢?
    首先,比如要创建5个子进程,直接写一个五次的循环是不行的,因为子进程也会执行父进程fork后面代码,5次循环将会有2^5=32个进程
    所以我们在子进程中加break,让子进程不再创建孙子进程
    同时,由于break后,i不再增加,所以一个i值对应一个子进程,甚是巧妙!
    这样,后面就可以使用i值作为分支条件,分别控制5个进程。
    (具体实现见最后的实践内容)

    而且父子进程共用一个文件描述符表,所以子进程的标准输入、输出、错误,都默认和父进程一样。

    如果父进程先于子进程终止,孤儿进程会被孤儿院回收(用户/内核init进程)

    wait和waitpid

    wait:回收子进程资源,阻塞回收任意一个。

    pid_t wait(int *status)
    参数 status(传出)回收进程的状态。
    返回值。成功。回收进程的pid
    失败。-1,errno

    阻塞等待子进程退出、清理子进程残留在内核的 pcb资源,通过传出参数,得到子进程结束状态

    获取子进程正常终止值:
    WIFEXITED(status)--》为真--》调用EXITSTATUS(status)--》得到子进程退出值。

    获取导致子进程异常终止信号。
    WIFSIGNALED(statusm) --》为真--》调用WTERMSIG(status) --》得到导致子进程异常终止的信号编号。

    waitpid函数。指定某一个进程进行回收。可以设置非阻塞。

    pid_t waitpid(pid_t pid ,int *status,int options)

    参数
    pid指定回收的子进程
    pid>0 : 待回收的子进程id
    pid=-1: 任意子进程
    pid=00:同组的子进程。

    options:WNOHANG指定回收方式为非阻塞。

    exec函数族

    该函数成功时没有返回值,调用成功后,进程就去执行另外一个程序

    所以,写在exec函数后面的代码,只有在exec出错时,才有可能执行

    原来进程的fork出来的代码段、数据段、堆栈等,都将被替换成新的程序的内容。但pid不变

    环境变量是为当前sh定义的变量,他们定义了程序的执行环境。
    使用 env 查看环境变量,export修改环境变量

    img

    管道和命名管道

    管道是用于进程交换数据的单向进程间通信通道,管道有一个读取端和一个写入端。

    命名管道是不相关进程间的FIFO通信通道。

    读取和写入管道通常是同步、阻塞操作。

    阻塞:
    读数据时:管道里没有数据,但是还有写入进程
    写数据时:管道里没有存储空间,但还有读出进程

    错误:
    读数据时:管道里没有数据,而且没有写入进程
    写数据时:管道没有读出进程

    命名管道是伪文件,本身不占用任何空间。是文件系统的一部分,并按目录进行组织。

    使用库函数mkfifo()或者系统调用mknod(X,S_IFIFO,X)创建命名管道

    问题与解决思路

    如何gdb调试带fork的程序?

    gdb默认进入fork的父进程

    可以使用命令 set follow-fork-mode child 进入使其进入子进程

    如果使用默认设置,gdb会先将fork出的子进程执行完毕,再接着执行父进程。

      1 #include <stdio.h>
      2 #include <unistd.h>
      3 #include <sys/types.h>
      4 
      5 int main(){
      6     int i;
      7     for(i=0;i<2;i++){
      8         fork();
      9         printf("-");
     10     }
     11     return 0;
     12 }
    

    这个程序的输出结果是8个'-',即 --------,刚开始确实令人摸不到头脑。

    第一次fork后,有2个进程,分别打印1个'-',共2个
    第二次fork后,有4个进程,分别打印1个'-',共4个
    一共应该只打印6个才对。

    后来将代码改成printf("-\n"),就正常打印了6个

    于是怀疑跟stdout的行缓冲有关。fork出来的子进程把缓冲区也复制了。

    第一次fork后,父进程缓冲区里有1个,大儿子缓冲区里有1个
    第二次fork后,printf之前,父进程缓冲区里有1个,大儿子缓冲区里有1个,二儿子复制了父进程缓冲区里的1个,孙子复制了大儿子缓冲区里的1个
    第二次fork后,printf之后,四个进程缓冲区都增加1个,共增加4个
    这样一共就会打印8个

    用gdb调试一下,验证猜想

    img

    到这里,大儿子和孙子进程执行结束,将缓冲区的内容打印出来,一共打印4个,符合猜想

    img

    到这里,二儿子进程执行结束,将缓冲区的内容打印出来,一共打印2个,符合猜想

    img

    最后父进程执行结束,程序退出,将缓冲区的内容打印出来,一共打印2个,符合猜想

    实践内容

    fork

    还有getpid()和getppid()

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <unistd.h>
      4 #include <pthread.h>
      5 
      6 int main(){
      7     printf("before fork 1\n");
      8     printf("before fork 2\n");
      9     printf("before fork 3\n");
     10 
     11     pid_t pid = fork();
     12     if(pid == -1){
     13         perror("fork error");
     14         exit(1);
     15     }else if(pid ==0 ){
     16         printf("---child : my id = %d , myparent id = %d\n",getpid(),getppid());
     17     }else if(pid >0){
     18         printf("---parent : my child id = %d,my id = %d , my parent id = %d\n",pid,getpid(),getppid());
     19     }
     20     printf("=========EOF==========\n");
     21     return 0;
     22 }
    

    nfork

    循环创建多个子进程
    孤儿进程被孤儿院回收了,所以ppid = 1

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <unistd.h>
      4 #include <pthread.h>
      5 
      6 int main(){
      7     int i;
      8     for(i=0;i<5;i++){
      9         if(fork()==0) break;
     10     }
     11     if(i==0){
     12         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
     13     }else if(i==1){
     14         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
     15     }
     16     else if(i==2){
     17         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
     18     }
     19     else if(i==3){
     20         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
     21     }
     22     else if(i==4){
     23         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
     24     }else if(i==5){
     25         printf("I'm parent,my id = %d\n",getpid());
     26     }
     27     return 0;
     28 } 
    

    产生孤儿进程的原因就是子进程没有抢过父进程,父进程拿到CPU之后直接全部执行完事了。
    解决:用sleep阻塞一下父进程即可。

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <unistd.h>
      4 #include <pthread.h>
      5 
      6 int main(){
      7     int i;
      8     for(i=0;i<5;i++){
      9         if(fork()==0) break;
     10     }
     11     if(i==0){
     12         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
     13     }else if(i==1){
     14         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
     15     }
     16     else if(i==2){
     17         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
     18     }
     19     else if(i==3){
     20         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
     21     }
     22     else if(i==4){
     23         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
     24     }else if(i==5){
     25         sleep(1);
     26         printf("I'm parent,my id = %d\n",getpid());
     27     }
     28     return 0;
     29 }
    

    execlp

    默认在/bin/寻找可执行文件

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <pthread.h>
      4 #include <unistd.h>
      5 
      6 int main(){
      7     pid_t pid=fork();
      8     if(pid == -1){
      9         perror("fork error");
     10         exit(1);
     11     }else if(pid == 0){
     12         //child
     13         execlp("ls","ls","-l",NULL);
     14         perror("exec error");
     15         exit(1);
     16     }else if(pid > 0){
     17         //parent
     18         sleep(1);
     19         printf("I'm parent : %d\n",getpid());
     20     }
     21     return 0;
     22 }
    

    img

    execl

    将可执行文件的路径作为第一个参数传入

    使用dup将执行结果重定向到./result.txt

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <pthread.h>
      4 #include <unistd.h>
      5 #include <fcntl.h>
      6 
      7 int main(){
      8     pid_t pid=fork();
      9     if(pid == -1){
     10         perror("fork error");
     11         exit(1);
     12     }else if(pid == 0){
     13         //child
     14         int fd = open("result.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
     15         if(fd<0){
     16             perror("open file error");
     17             exit(1);
     18         }
     19         dup2(fd,STDOUT_FILENO);
     20         execl("/bin/date","date",NULL);
     21         perror("exec error");
     22         exit(1);
     23     }else if(pid > 0){
     24         //parent
     25         sleep(1);
     26         printf("I'm parent : %d\n",getpid());
     27     }
     28     return 0;
     29 }
    
  • 相关阅读:
    傻逼Eclipse笔记
    Less笔记
    [转]解决WebClient或HttpWebRequest首次连接缓慢问题
    Css3图标库
    Json.Net4.5 序列化问题
    async和await
    CLR、内存分配和垃圾回收
    C#7.0新语法
    C#6.0新语法
    C#泛型详解
  • 原文地址:https://www.cnblogs.com/kenneth2012/p/16773354.html
Copyright © 2020-2023  润新知