Linux系统编程----进程

目录

系统调用 库函数

1.进程相关概念

1.1程序和进程

1.2cpu MMU 虚拟内存

1.3 PCB进程控制块

1.4 进程状态 

2.环境变量

3.进程控制

3.1 fork创建子进程

3.1.1 打印父子进程

 3.1.2 循环顺序创建多个子进程(子进程不fork)

3.2 父子进程的比较 

3.2.1 父子进程共享

4.exec函数族

5.回收子进程

5.1进程终止

5.2 孤儿进程 僵尸进程(主要是理解概念)

5.3 wait

5.3.1 wait获取子进程退出值和异常终止信号

5.4 waitpid函数

5.4.1 waitpid回收指定子进程 

 5.5 回收多个子进程


系统调用 库函数

系统调用: 访问了内核的数据结构或者访问硬件资源     

库函数:c语言标准库     

查看函数具体信息:终端输入命令

系统调用: man 2 fork     //fork是函数名可替换

库函数: man 3 fopen        //fopen是函数名可替换

1.进程相关概念

1.1程序和进程

程序:编译好的二进制文件 例如a.out,不占用系统资源(只占用磁盘空间

进程跃的程序,运行起来的程序,需要消耗系统资源(内存

  • 可以把程序看作剧本,进程看作正在演的戏,演戏需要剧本,并且一个剧本可以有多场戏同时演出,所以程序与进程之间的关系是

并发:在操作系统中,一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态。但任意时刻都仍只有一个进程在进行。

并行:指在同一时刻,有多条指令在多个处理器上同时执行

1.2cpu MMU 虚拟内存

cpu(中央处理器)

MMU(虚拟内存映射管理单元)

MMU作用

  • 虚拟内存与物理内存的映射
  • 设置修改内存访问级别
  • 借助MMU虚拟内存映射,512M的物理内存运行程序也是4G的地址空间(虚拟地址,不真正存在)

虚拟内存作用

  • 避免用户直接访问物理内存地址,防止一些破坏性操作,保护操作系统;
  • 每个进程都被分配4GB的虚拟内存,用户程序可使用比实际物理内存更大的地址空间。
  • 当进程需要实际访问内存时候,会由内核的[请求分页机制]产生[缺页异常]调用物理内存页。

1.3 PCB进程控制块

本质:结构体 struct tast_struct{...} ...find usr/src/ -name sched.h

存储目录位置:

PCB进程控制块用来存放进程的相关信息内容:

  1. 进程id:         系统中每个进程有唯一的id,一个非负整数                                                      ps aux 返回结果里,第二列是进程id
  2. 文件描述符表  
  3. 进程状态:    初始态、就绪态、运行态、挂起态、终止态。
  4. 进程切换时需要保存和恢复的一些CPU寄存器
  5. 描述虚拟地址空间映射信息(比如说这里存着虚拟地址真正对应的物理地址)
  6. 描述控制终端信息(是否需要终端)
  7. 当前进程工作目录位置
  8. *umask掩码 (进程的概念)(umask默认002)
  9. 信号相关信息资源:未决信号集、信号屏蔽字
  10. 用户id和组id
  11. 进程可以使用的资源上限

1.4 进程状态 

进程状态有五种:初始态、就绪态、运行态、挂起态、终止态

  1. 创建状态:当一个进程被创建时,它处于创建状态。在这个阶段,操作系统为进程分配必要的资源(将代码和数据拷贝到内存,创建PCB结构体等),并为其分配一个唯一的进程标识符(PID)。
  2. 就绪状态:进程就绪状态是指进程已经满足了运行的条件,进程PCB被调度到CPU运行队列中,排队等待系统分配CPU资源来执行的状态。
  3. 运行状态:进程PCB被调度到CPU运行队列中且已被分配CPU资源,就叫做运行态。在这个阶段,进程的指令会被执行,它可以访问CPU和其他系统资源。只有运行状态下的进程才能占用CPU资源。
  4. 挂起状态 :当内存不足时,如果一个进程长时间不执行或者处于低优先级状态,操作系统可能会将其代码和数据置换出内存并存储到磁盘上的swap分区中。其PCB(进程控制块)仍然存在于进程表中。这是因为挂起状态只是进程的一种状态,表示进程暂时无法执行,但仍然需要保留进程的信息以便后续恢复执行。这样可以释放内存资源,给其他优先级较高的进程提供更多的执行机会。这些被置换到磁盘中的进程的状态就叫做挂起。
  5. 终止状态:当进程完成其任务或被操作系统终止时,它进入终止状态。在这个阶段,进程可以释放所有已分配资源,并从系统中移除

2.环境变量

终端使用命令可以查看系统所有的环境变量

env

 终端查看某条环境变量对应的值

echo $环境变量名

env|grep 环境变量名

环境变量语法格式

环境变量名=值:值:值:值 

常用环境变量

1)PATH

可执行程序的搜索目录,可执行程序包括Linux系统命令和用户的应用程序,PATH变量的具体用法本文后面的章节中有详细的介绍。

2)LANG

Linux系统的语言、地区、字符集,LANG变量的具体用法本文后面的章节中有详细的介绍。

3)HOSTNAME

服务器的主机名。

4)SHELL

用户当前使用的Shell解析器。

5)HISTSIZE

保存历史命令的数目。

6)USER

当前登录用户的用户名。

7)HOME

当前登录用户的主目录。

8)PWD

当前工作目录。

9)LD_LIBRARY_PATH

C/C++语言动态链接库文件搜索的目录,它不是Linux缺省的环境变量,但对C/C++程序员来说非常重要,具体用法本文后面的章节中有详细的介绍。

10)CLASSPATH

JAVA语言库文件搜索的目录,它也不是Linux缺省的环境变量,但对JAVA程序员来说非常重要,具体用法本文后面的章节中有详细的介绍。

获取环境变量的函数getenv

#include <stdio.h>
#include <stdlib.h>

int main ()
{
   printf("PATH : %s\n", getenv("PATH"));
   printf("HOME : %s\n", getenv("HOME"));
   printf("ROOT : %s\n", getenv("ROOT"));

   return(0);
}

拓展:

系统环境变量、用户环境变量 参考  https://blog.csdn.net/qq_41962968/article/details/122152904

3.进程控制

3.1 fork创建子进程

pid_t fork(void)
    创建子进程。父子进程各自返回。父进程返回子进程pid。 子进程返回 0.
pid_t getpid(void);获取当前进程 ID
pid_t getppid(void);获取当前进程的父进程 ID

  1. 父子进程共同争夺cpu资源,执行的先后顺序随机。 
  2. 子进程fork创建出来后会继续执行后面的代码,fork之前的代码子进程不会执行但是子进程共享这个代码段

代码实现

3.1.1 打印父子进程

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
 
int main() {
    pid_t pid;  
    pid = fork();      //创建子进程
    if (pid < 0) {     //fork返回值小于0 创建子进程失败
        perror("fork error");
    }
    if (pid == 0){    //fork返回值等于0 表示正在运行的是子进程
        printf("I am child pid = %d my father pid = %d\n",getpid(),getppid());
    }
    else {           //fork返回值大于0 表示正在运行的是父进程 返回值是子进程的Pid
        printf("I am father pid = %d my father pid = %d\n",getpid(),getppid());
        sleep(5);  //该程序父进程的父进程可以理解为是bash终端,子进程执行结束后会通知父进程父进程也执行完就会释放资源,如果不sleep ,bash 就有可能因为父进程的通知而释放资源导致 fork出来的子进程的打印在终端提示符后
    }
    return 0;
}

 3.1.2 循环顺序创建多个子进程(子进程不fork)

# include<stdio.h>
# include<stdlib.h>
# include<unistd.h>
int main()
{
  pid_t pid;
  int i;
  for(i=0;i<5;i++)
  {
      pid =fork();
      if(pid==0)
      {
       break;   //子进程跳出不参与循环,避免子进程也fork出子进程
      }
  }
  if(5==i)//父进程会完整执行完for循环,子进程会跳出,所以只有父进程的i值才会是5
  {
      sleep(5);
      printf("I am father\n");
  
  }
  else//子进程 提前break跳出i不等于5就会进到else 打印
  {
      sleep(i);
      printf("I am %d child \n",i+1);
  }
 
  return 0;
}

子进程也参与循环fork"孙进程"的情况: 

 

3.2 父子进程的比较 

比较:值是否相同,共享:是否使用同一地址空间

父子进程相同:刚fork后。 data段、text段、堆、栈、环境变量、全局变量、宿主目录位置、进程工作目录位置、信号处理方式
父子进程不同:进程id、fork的返回值、各自的父进程、进程创建时间、闹钟(定时器)、未决信号集

3.2.1 父子进程共享

  • 读时共享写时复制。(当是读的操作的时候全局变量共用一个地址空间,当写操作的时候会进行赋值拷贝后在写入,不使用原地址空间)
  • 父子进程之间不共享全局变量(意思就是父(子)进程对全局变量做了赋值,子(父)进程的全局变量不会改变。)
  • 父子共享: 1、文件描述符(打开文件的结构体)   2. mmap映射区

:fork之后父进程先执行还是子进程先执行不确定,取决于内核所使用的调度算法

4.exec函数族

调用系统命令 如“ls”或 自定义命令

fork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种 exec 函数以执行另一个程序

创建出的子进程与父进程有相同的 data段、text段,通过调用exec函数族可以进程 ID不变的同时替换(用户空间代码和数据完全被新程序替换).text、.data为所要加载的程序的.text、.data。

int execl(const char *path, const char *arg, ...);  //path是绝对路径
int execlp(const char *file, const char *arg, ...);//默认环境变量的path路径+file参数
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[])
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

例:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

int mian(int argc,char *argv[])
{
    pid_t pid = fork();
    if(pid == 0)
    {    
      //execlp("命令文件名","命令名","命令参数","命令参数",.....NULL) 
//execlp函数执行成功无返回值失败返回-1
        execlp("ls","ls","-l","-t","r",NULL); //NULL是哨兵表示参数结束
        perror("bin/ls erroor");
        exit(1);
    }
    else(pid > 0)
    {
        sleep(2);
        printf(" I am parent");
    }
    return 0; 
}

5.回收子进程

父进程有义务在子进程执行结束后回收子进程,隔辈进程无需回收

5.1进程终止

  1. 关闭所有文件描述符
  2. 释放在用户空间分配的内存
  3. 进程PCB残留在内核中保存了一些信息:
  •         如果是正常终止则保存着退出状态值
  •         如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或者waitpid获取这些信息,然后彻底清除掉这个进程

5.2 孤儿进程 僵尸进程(主要是理解概念)

孤儿进程父进程先于子进程终止,子进程就变成孤儿进程了,会被init进程领养,init进程可以称作进程孤儿院。

僵尸进程子进程终止父进程未终止,但尚未对子进程进行回收 ,在此期间子进程被称为僵尸进程(死了没人收

终端到的僵尸进程:

5.3 wait

wait函数:    回收子进程退出资源, 阻塞回收任意一个。
 
    pid_t wait(int *status)
 
    参数:(输出) 回收进程的状态。     
 
    返回值:

        成功: 回收进程的pid
 
        失败: -1, errno
 

wait(NULL);参数传NULL表示不要子进程的pcb 退出状态 就只完成回收的作用

 wait 函数作用

  •     函数作用1:    阻塞等待子进程退出                                                                                                   (阻塞等待是指在wait()函数后面的代码不执行等子进程回收完毕后再执行)
  •     函数作用2:    回收(清理)子进程残留在内核的 pcb 资源
  •     函数作用3:    通过传出参数,得到子进程退出状态(正常、异常)

wait 获取子进程退出值异常终止信号 

  1.   首先wpid = wait(&status);  获得退出状态和子进程pid
  2.   其次if(WIFEXITED(status)) 为真->>调用 WEXITSTATUS(status) 得到 子进程 退出值
  3.   最后if(WIFSIGNALED(status)为真->>调用 WTERMSIG(status) 得到 导致子进程异常终止的信号编号    

代码如5.3.1

5.3.1 wait获取子进程退出值和异常终止信号

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    pid_t pid,wpid;
    int status;
    pid =fork();
    if(pid==0)
    {
        printf(" i am child,my id is%d\n",getpid());
        printf("child die\n");
        return 73;
    }
    else if(pid>0)
    {
        //wpid=wait(NULL);不关心怎么结束的
        wpid = wait(&status);//等待子进程结束
        if(wpid==-1)
        {
            perror("wait error");
            exit(1);
        }
        if(WIFEXITED(status))//判断 子进程正常退出判断
        {
            printf("child exit with%d\n",WEXITSTATUS(status));
            printf("------parent  finish\n");
        }
        if(WIFSIGNALED(status))//判断 子进程异常退出判断
        {
            printf("child exit with%d\n",WTERMSIG(status));
        }
    }
    else
    {
        perror("fork");
        return 1;
    }
    
}

5.4 waitpid函数

指定某一个进程或任一一个进程进行回收。可以设置非阻塞。(非阻塞就是waitpid()后面的代码不用等待子进程回收完就可以执行)

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

            
waitpid(-1, &status, 0) == wait(&status);就是回收任意一个子进程,并且阻塞
 
    pid_t waitpid(pid_t pid, int *status, int options)
 
    waitpid函数参数:

  1.  pid:指定回收某一个子进程pid
    •  pid > 0  待回收的子进程pid
    • pid = -1 任意子进程
    • pid = 0 同组的子进程(同一个父进程创建出的子进程默认会加入到一组)。
  2.  status:(输出) 回收进程的状态
  3.  options:WNOHANG 指定回收方式为,非阻塞

 
    返回值:
 
        返回值> 0 : 表成功回收的指定pid的子进程
 
       返回值 =0 : 函数调用时, 参3 指定了WNOHANG, 并且,没有子进程结束。
 
       返回值 = -1: 失败。errno

5.4.1 waitpid回收指定子进程 

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/wait.h>  
#include <pthread.h>  
  
  
int main(int argc, char *argv[])  
{  
    int i;  
    pid_t pid, wpid, tmpid;  
  
    for (i = 0; i < 5; i++) {         
        pid = fork();  
        if (pid == 0) {       // 循环期间, 子进程不 fork   
            break;  
        }  
        if (i == 2) {  
            tmpid = pid;  
            printf("--------pid = %d\n", tmpid);  
        }  
    }  
  
    if (5 == i) {       // 父进程, 从 表达式 2 跳出  
        sleep(5);  
  
        //wait(NULL);                           // 一次wait/waitpid函数调用,只能回收一个子进程.  
        //wpid = waitpid(-1, NULL, WNOHANG);    //回收任意子进程,没有结束的子进程,父进程直接返回0   
        //wpid = waitpid(tmpid, NULL, 0);       //指定一个进程回收, 阻塞等待  
        printf("i am parent , before waitpid, pid = %d\n", tmpid);  
  
        wpid = waitpid(tmpid, NULL, WNOHANG);   //指定一个进程回收, 不阻塞  
        //wpid = waitpid(tmpid, NULL, 0);         //指定一个进程回收, 阻塞回收  
        if (wpid == -1) {  
            perror("waitpid error");  
            exit(1);  
        }  
        printf("I'm parent, wait a child finish : %d \n", wpid);  
  
    } else {            // 子进程, 从 break 跳出  
        sleep(i);  
        printf("I'm %dth child, pid= %d\n", i+1, getpid());  
    }  
  
    return 0;  
} 

输出:

 5.5 回收多个子进程

不管是wait还是waitpid一次都只能回收一个子进程,要想回收多个,就是在父进程中加入while循环

// 回收多个子进程  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/wait.h>  
#include <pthread.h>  
  
int main(int argc, char *argv[])  
{  
    int i;  
    pid_t pid, wpid;  
  
    for (i = 0; i < 5; i++) {         
        pid = fork();  
        if (pid == 0) {       // 循环期间, 子进程不 fork   
            break;  
        }  
    }  
  
    if (5 == i) {       // 父进程, 从 表达式 2 跳出  
        /* 
        while ((wpid = waitpid(-1, NULL, 0))) {     // 使用阻塞方式回收子进程 
            printf("wait child %d \n", wpid); 
        } 
        */  
        while ((wpid = waitpid(-1, NULL, WNOHANG)) != -1) {     //使用非阻塞方式,回收子进程.  
            if (wpid > 0) {  
                printf("wait child %d \n", wpid);  
            } else if (wpid == 0) {    //waitpid返回值是0时表示没有子进程结束
                sleep(1);  
                continue;  //跳出本次循环,进入下一次while循环
            }  
        }  
  
    } else {            // 子进程, 从 break 跳出  
        sleep(i);  
        printf("I'm %dth child, pid= %d\n", i+1, getpid());  
    }  
  
    return 0;  
}  

参考文章: https://blog.csdn.net/zhaojiazb/article/details/129375637

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值