Linux多任务编程(一)

前言

为什么学习多进程线程:

        在我们学习该课程之前,我们代码的执行逻辑,从上往下,一条一条语句执行: 先干什么,后干什么。学习多线程进程,可以实现多任务同时运行,互相切换互不影响,拜托传统代码逻辑执行的弊端。任务会频繁切换给用户多个任务一同运行的感觉。

什么是任务:

简单讲:一个任务就是一个程序的一次运行,一个任务包含一个或者多个独立功能的子任务,这个独立的子任务是进程或者线程。

一、进程基础

 1.1 进程的概念

  1.1.1 什么是进程

        进程是程序执行和资源管理的最小单位,是一个动态的概念,它是程序执行的过程,包括创 建、调度和消亡。

  1.1.2 什么是程序

        程序:静态指令的集合,程序就相当于你的计划书。

  1.1.3 进程与程序的区别

        程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念, 进程是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡一个程序可以对应多个进程,一个进程只能对应一个程序。

  1.1.4 进程包含什么

        (1)“数据段”存放的是全局变量、常数以及动态数据分配 的数据空间(如malloc函数取得的空间)等。

        (2)“正文段”存放的是程序中的代码。

        (3)“堆栈段”存放的是函数的返回地址、函数的参数以及 程序中的局部变量 。

  1.1.5进程的特点:
(1)动态性:具有完整的生命周期,生命周期内是动态变化的。还具有动态地址空间
(2)并发性:宏观上同时,微观上分先后
(3)独立性:各个进程之间互不干扰,除非涉及进程间通信
(4)异步性:每个进程的执行都是由CPU调度的,各个之间可能不一样,也有可能一样

  1.2进程的管理手段:

        windows: 管理手段:任务管理器        进程处理者:CPU

         linux:   管理手段:PCB >>Process Control Block (进程管理块)

 是linux系统用来管理进程的手段,由内核创建并维护的 是一个非常巨型结构体: struct task_struct

进程处理者:CPU

  1.2.1任务状态变迁

  1.2.2 进程的分类:

(1)交互式进程  : 由shell终端产生并控制的   如:./a.out

(2)批处理进程  : shell脚本:指令一批一批执行 

(3)守护进程    : 周期性的在后台等待或执行某一个任务      如: ssh  samba  tftp nfs  木马

  1.2.3进程的查看:
 查看状态:
ps -aux   :静态查看进程
top       :动态查看进程(每三秒刷新一次)

USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

USER:进程拥有者 
PID :进程的ID号,是进程的唯一标识,不重复 :越早开启的进程,PID越小 
%CPU: CPU的占用率 
%MEM:物理内存占用率 
VSZ :虚拟内存用量 
RSS :物理内存用量        
TTY :终端的使用,如果该进程没有终端:显示? 
STAT:进程的状态 

进程状态:
S:休眠(可以中断)    D:休眠(不可中断)    I:空闲 Idie    T:暂停     X:死亡    Z:僵尸状态      R:正在运行    s:进程的领导者(多进程)     <:优先级高     N:优先级低    l:线程的领导者(多线程) +:前端进程


休眠(可中断):进程处于阻塞状态,正在等待某些事件发生或能够占用某些资源。处于这种状态下的进程可以被信号中断
僵死状态:子进程运行结束,父进程未退出,并且未使用wait函数等系统调用来回收子进程的退出状态。





START:进程的开始执行时间
TIME :运行时间
COMMAND:产生进程的命令 :./a.out 

优先级:
        linux系统有140个优先级:数字越小,优先级越高 
            实时进程: 0 - 99   100个
            非实时:  100- 139  40个 
            通过:ps -le查看优先级
            PRI  NI
            PRI:优先级 
            NI :谦让值(-20 ~ 19 ) 数字越小,越不谦让

 1.3进程的调度

        进程互斥:

进程互斥是指当有若干进程都要使用某一共享资源时,任何时候最多允许一个进程使用,其他要 使用该资源的进程必须等待,直到只用自愿者释放了该资源。

        进程同步:

一组并发进程按一定的顺序执行的过程称为进程间的同步。具有同步关系的一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。

        临界资源:

 操作系统中将以此只允许一个进程访问的资源称为临界资源。

        临界区:

进程中访问临界资源的那段程序代码称为临界区。为实现对临界资源的互斥访问,应保证诸进程互斥地进入各自的临界区。

进程的调度:

        抢占式和非抢占式调度

进程调度算法:
        
        先来先服务调度算法
        短进程优先调度算法
        高优先级优先调度算法
        时间片轮转法

二、进程的API学习:

 2.1 进程的创建 : 

 生(fork)

#include <sys/types.h>
        #include <unistd.h>
        pid_t fork(void);
        功能: 
            创建子进程 
            
        参数:
            void:空 
            
        返回值: 
            创建成功: 
                父进程返回子进程的PID号 
                子进程返回0 
                
            失败: 
                返回-1,并设置错误码
    2.1.1fork()特点:
1>创建出来的进程是以什么样的方式存在? 
                在fork()执行后的下面所有的代码,都会被父子进程共同执行
          
2>同一程序,多次fork,得出来的进程个数,是有规律的,参考图片 
 
3>fork以下的所有语句,都会被父子进程共同执行 如何限定操作:将父子进程的操作区分开:
 父子进程:返回值有区别,所以我们要从两者的返回值入手 
 父进程的返回值:子进程的PID号 PID号必会大于0 
    
    子进程返回值:0 
                if(pid>0){
                    //父进程的操作
                }else if(pid==0){
                    //子进程的操作 
                }else{
                    //失败操作
                }
2.1.2获取pid
        1>如何在进程中获取PID号? 
            1>获得本身的PID号
             #include <sys/types.h>
             #include <unistd.h>
             pid_t getpid(void);
             功能: 
                    获得执行该函数的进程的PID号 
                    
             返回值: 
                    成功返回该进程的PID号 
                    失败返回-1,并设置错误码
              
            2>如何在进程中获取其父进程的PID号             
                #include <sys/types.h>
                #include <unistd.h>
                pid_t getppid(void);
             功能: 
                    获得执行该函数的进程的父进程的PID号 
                    
             返回值: 
                    成功返回该父进程的PID号 
                    失败返回-1,并设置错误码

 

2.1.3父子进程关系
父子进程之间的关系: 
    两个进程无不干扰:
     1>杀死父进程,保留子进程:功能正常运行                  
     2>保留父进程,杀死子进程:子进程会变成僵尸状态
             原因:子进程所占用系统资源并没有被释放 死了,但没完全死
                                                 
             解决:杀死父进程,让系统介入回收资源,父进程执行收尸操作,回收子进程资源

父进程的父进程为终端。
执行终端:控制终端

 2.2 进程的操作替换 

     何时使用:

          (1)当进程认为自己不能再为系统和用户做出任何 贡献了时就可以调用exec函数,让自己执行新 的程序

          (2)如果某个进程想同时执行另一个程序,它就可 以调用fork函数创建子进程,然后在子进程中 调用任何一个exec函数。这样看起来就好像通 过执行应用程序而产生了一个新进程一样

  2.2.1exec函数族
exec函数族
#include <unistd.h>
   extern char **environ;//外部引入
   int execl(const char *path, const char *arg, ...
                       /* (char  *) NULL */);
    int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
    int execle(const char *path, const char *arg, ...
                       /*, (char *) NULL, char * const envp[] */);
    int execv(const char *path, char *const argv[]);
    int execvp(const char *file, char *const argv[]);
    int execvpe(const char *file, char *const argv[],
                       char *const envp[]);
      分析: 
        1>exec开头的:exec函数族
        2>带p的:表示根据文件名寻找要替换的程序 ,默认回去/bin  和/usr/bin 去找不带p的:表示根据路径寻找要替换的程序
        3>带l的:表示以列举的方式寻找要替换的程序
        4>带v的:表示以数组的方式寻找要替换的程序 
        5>带e的:表示以环境变量的方式传递数据,并且替换程序
   
     返回值: 
       只有发生错误时才会有返回值 
       失败返回-1,并设置错误码

    测试:

        以最后以一个execvpe函数为例:

 char *envp[]={"hello"," world",NULL};  //数组最后以NULL结尾,表示读取到最后条件

execvpe函数则是讲程序跳转到new可执行程序中。讲数组的内容写入到grgv中,最后结束程序。

2.2.2 补充:Linux环境变量的修改:
 1>临时修改:只对当前终端有效:一次性
            export PATH=$PATH:/home/pm/shared/pro/day2_code  
            
 2>永久修改:只以用户为单位的情况下修改:针对普通用户
                1>打开用户的环境变量文件 
                    sudo vim ~/.bashrc
                    
                2>在里面添加一句话
                export PATH=$PATH:/home/pm/shared/pro/day2_code
                
                3>保存并退出 
                    source ~/.bashrc 
                    
 3>永久修改:所有用户都可以用 :针对所有
                1>执行命令: 
                    sudo vim /etc/environment 
                    
                2>在文件中添加
                :/home/pm/shared/pro/day2_code
                
                3>保存并退出

2.3进程阻塞

1>病:进程的阻塞: 
      进程互斥
进程互斥是指当有若干进程都要使用某一共享资源时,任何时候最多允许一个进程使用,其他要 使用该资源的进程必须等待,直到只用自愿者释放了该资源
      进程同步
一组并发进程按一定的顺序执行的过程称为进程间的同步。具有同步关系的一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。
      临界资源
操作系统中将一次只允许一个进程访问的资源称为临界资源
      临界区
进程中访问临界资源的那段程序代码称为临界区。为实现对临界资源的互斥访问,应保证诸进程互斥地进入各自的临界区。
2.3.1 进程的阻塞

        flock --->给文件描述符指代的临界资源上锁

#include <sys/file.h>
int flock(int fd, int operation);
    功能: 
  给fd(文件描述符) 做 上锁操作 
                   
     参数: 
          fd:文件描述符 
          operation:锁的操作 
             LOCK_SH:共享锁(上锁) //可以双方同时操作
             LOCK_EX:互斥锁(上锁)
             LOCK_UN:解锁 
            LOCK_NB:不上锁
     返回值: 
           成功返回0 
           失败返回-1,并设置错误码

flock_1.c:

flock_2.c:

结果:

 1.txt文件内容:

2.4进程的退出

                进程有哪几种死法: 
                    1>老死     -->程序运行完毕  
                    2>被杀死   -->接收到 kill -9 信号 
                    3>自杀     -->提前遇到了return  和 exit  _exit 
                 
                自杀: 1>exit     2>_exit    3>return
                    
                1>exit --->结束进程
                #include <stdlib.h>
                void exit(int status);
                功能: 
                      结束进程  
                      
                参数: 
                      status:进程退出时的状态:进程的遗言
                      
                2>_exit --->结束进程
                #include <unistd.h>
                void _exit(int status);
                
                功能: 
                       结束进程 
                       
                参数: 
                        status:进程退出时的状态:进程的遗言
                        
                总结: 
                    exit在结束进程时,会刷新缓冲区
                    _exit直接结束进程,不会做其他任何操作
                    
                3>return 
                  return用来结束函数的 
                  但是可以通过结束main函数来达到结束进程的目的 
2.4.1 测试:

遇到exit函数正常退出,则不执行之后的程序,且会刷新缓冲区

   

 遇到_exit函数异常退出,则不执行之后的程序,不会刷新缓冲区。

 

2.5进程的收尸

进程的收尸: 
                收尸:释放进程运行时向系统申请的资源 
                概念:只能父进程给子进程收尸 
                1>wait
                #include <sys/types.h>
                #include <sys/wait.h>
                pid_t wait(int *wstatus);
                功能: 
                        收尸操作:带阻塞 
                        父进程阻塞等待子进程死亡,子进程死亡后做收尸操作 
                        
                参数: 
                      wstatus:接收收尸子进程的遗言 

                返回值: 
                        成功返回 被收尸子进程PID号 
                        失败返回-1,并设置错误码
                PS: 
                    wait有两套收尸逻辑: 
                        1>谁先死,收谁尸  --->优先
                        2>谁的死亡时间越接近父进程执行收尸的操作时间,收
                          谁的尸

                2>waitpid-------!!!!!!!!!!!
                #include <sys/types.h>
                #include <sys/wait.h>
                pid_t waitpid(pid_t pid, int *wstatus, int options);
                功能: 
                      给子进程收尸 --->指定收尸
                      
                参数: 
                        PID:进程号:取值范围 -----!!
                        >0: 给特定的子进程收尸(进程号为PID) 
                                例子:waitpid(12345,..,..)
                        =0:给当前进程在同一进程组内的子进程收尸
                        =-1:给任意子进程收尸,无论他在哪
                        <-1:给 进程组ID号为 |pid| 下的子进程收尸
  -12345  ---> 12345  然后收掉12345下子进程的尸                
                        wstatus:子进程的死亡状态 
                        
                        options:
                                0:代表阻塞等待收尸 
                                WNOHANG:不阻塞直接收尸 
                                
                                WUNTRACED:分析子进程的死因--->验尸   -----!!
                                    WIFEXITED:如果子进程正常死亡,则为true
                                        采用WEXITSTATUS 去获取死因
                                
                                    WIFSIGNALED:如果子进程是被信号杀死,则为true 
                                        采用WTERMSIG 去获取死因
                                         
                                    WIFSTOPPED:如果子进程是被信号暂停,则为true
                                        采用WSTOPSIG 去获取死因
               返回值: 
                    如果阻塞状态下成功返回 被收尸的子进程PID号 
                    如果不阻塞状态下:
                        成功返回 被收尸的子进程PID号
                        失败返回 0  -->没收到尸 
                    失败返回-1,并设置错误码

wait函数:

调用该函数使进程阻塞,直到任一个子进程结束 或者是该进程接收到了一个信号为止。如果该进 程没有子进程或者其子进程已经结束,wait 函数 会立即返回。
waitpid 函数:
功能和 wait 函数类似。可以指定等待某个子进程 结束以及等待的方式(阻塞或非阻塞)。

测试:

 程序流程图:

多线程通信t:http://t.csdn.cn/on1EI

猜你喜欢

转载自blog.csdn.net/apple_71040140/article/details/132480579