进程总结

进程总结


1.进程概念
  • 进程是运行中的程序,程序是保存在硬盘上的可执行代码

  • 进程内部又划分了许多线程,线程基本上不拥有系统资源,他与同属一个进程的其他线程共享进程的全部资源。

  • 进程在执行过程中拥有独立的内存单元。内部线程可共享这些内存,一个线程可以创建和撤销另一个线程,同一个线程中的多个进程可以并行执行。

  • ***ps或pstree***可以查看当前的进程


2.进程标识

获得进程各种标识符的函数表

#include<unistd.h>

函数声明功能
pid_t getpid(id)获得进程ID
pid_t getppid(id)获得父进程ID
pid_t getuid()获得进程的实际用户ID
pid_t geteuid()获得进程的有效用户ID
pid_t getgid()获得进程的实际组ID
pid_t getegid(id)获得进程的有效组ID
  • 某个普通用户A,运行了一个程序,而这个程序是以root身份来运行的,这程序就有root权限。实际:A的ID,有效:root的ID

3.进程结构
  • 名称内容
    代码段存放程序可执行代码
    数据段存放全局变量,常量,静态变量
    存放动态分配的内存变量
    用于函数调用,存放函数的参数,函数内部定义的局部变量

4.进程状态
  • 运行状态®:正在运行或在运行队列中等待运行

  • 可中断等待状态(S): 进程正在的等待某个事件完成(如等数据到达),等待过程中可以被信号或定时器唤醒。

  • 不可中断等待状态(D): 等待过程中不可以被唤醒,必须等待直到等待的事情发生

  • 濒死状态(Z):进程已终止,进程描述符依然在,直到父进程调用wait函数后释放

  • 停止状态(T):进程因为受到信号(SIGSTOP,SIGSTP,SIGTIN,SIGTOU)后停止运行,或者该进程正在被跟踪(调式程序时,进程处于被跟踪的状态)

    • ps -eo pid,user,args

      参数 -e 显示所有进程信息,-o 参数控制输出。Pid,User 和 Args参数显示PID,运行应用的用户和该应用

    • <(高优先级进程) N(低优先级进程) L(内存锁页,即页不可以被唤出内存) s(该进程为会话首进程) l(多线程进程) +(进程位于前台进程组)


进程控制
  • 有:创建进程,执行新程序,退出进程,改变进程优先级
  • 对进程进行控制的主要系统调用:
    • fork:创建一个新进程
    • exit:终止进程
    • exec:执行一个应用程序
    • wait:将父进程挂起,等待子进程终止
    • getpid:获取当前进程的进程ID
    • nice:改变进程优先级

创建进程
  • fork

    • 作用:创建进程

    • 原型:

      #include<sys/types.h>
      #include<unistd.h>
      pid_t fork(void);
      
    • 两个返回值(调用一次返回两次)

      • 调用fork函数后,当前进程实际上已经分裂成两个进程了,一个是原来的父进程,一个是刚创建的子进程。父子进程在fork函数的地方分开。

      • 两个返回值:

        • 父进程的返回值是刚创建的子进程的ID
        • 子进程的返回值是0
      • 创建成功:fork函数返回两次

      • 创建失败:返回-1

        当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
        系统内存不足,这时errno的值被设置为ENOMEM。
        
    • 父子进程是交替运行的,谁先运行取决于内核使用的调度算法。(因为操作系统一般让进程享有同等执行权)

    • 子进程继承的父进程的有:

      • 用户ID,组ID,当前工作目录,根目录,打开的文件,创建文件中使用的屏蔽字,信号屏蔽字,上下文环境,共享的存储段,资源限制等
    • 子进程有一些自己的特性:

      • 子进程有ta唯一的ID
      • fork的返回值不同,父进程返回子进程的ID,子进程返回0
      • 不同父进程的ID
      • 子进程共享父进程打开的文件描述符,但父进程对文件描述符的改变不会影响子进程中的文件描述符
      • 子进程不能继承父进程设置的文件锁
      • 子进程不继承父进程设置的警告
      • 子进程的未决信号集被清空
        • 未决信号集:没有处理信号的集合
          • 未决:一种状态,指的是从信号产生到信号被处理前的这段时间
        • 阻塞信号集:要屏蔽的信号的集合
          • 阻塞:是一个开关的动作,指的是阻止信号被处理,但不是阻止信号产生
孤儿进程
  • 如果一个子进程的父进程先于子进程结束,子进程就成为了一个孤儿进程,ta由init进程(1)收养,成为init进程的子进程。

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<unistd.h>
    int main(void)
    {
        pid_t pid;
        pid=fork();
        switch(pid)
        {
            case 0:
            {
                while(1)
                {
                    printf("A background process,PID:%d\n,parentID:%d\n",getpid(),getppid());
                    sleep(3);
                }
            }
            case -1:
            {
                perror("process creation failed\n");
                exit(-1);
            }
            default:
            {
                printf("I am parent process,my pid is %d\n",getpid());
                exit(0);
            }
        }
        return 0;
    }
    
僵尸进程
  • 一个进程使用fork创建子进程,如果子进程退出,而父进程没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,此进程称为僵尸进程
vfork
  • vfork与fork区别:
    • 都是调用一次返回两次
    • fork:创建一个子进程,子进程完全复制父进程的资源,得到的子进程独立于父进程
    • fork:创建一个子进程,操作系统并不是把父进程的地址空间完全复制给子进程。创建的子进程共享父进程的地址空间,即子进程完全运行在父进程的地址空间,子进程对地址空间中任何数据的修改同样为父进程所见。
    • fork:那个进程先运行取决于系统
    • vfork:保证子进程先运行,当它调用exit或exec后,父进程才可以运行。如果在使用exec或exit之前子进程要依赖于父进程的某个行为,就会导致死锁
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
int globVar=5;
int main(void)
{
    pid_t pid;
    int var=1;
    int i;
    printf("fork is differernt with vfork\n");
    pid=fork();
    //pid=vfork();
    switch(pid)
    {
        case 0:
        {
            i=3;
            while(i-->0)
            {
                printf("child process is running\n");
                globVar++;
                var++;
                sleep(1);
            }
            printf("child's globVar=%d,var=%d\n",globVar,var);
            exit(0);
        }
        case -1:
        {
            perror("process creat default\n");
            exit(0);
        }
        default:
        {
            i=5;
            while(i-->0)
            {
                printf("parent process is running\n");
                globVar++;
                var++;
                sleep(1);
            }
            printf("parent's globVar=%d,var=%d\n",globVar,var);
            exit(0);
        }
    }
}

对fork:不管是全局变量还是局部变量,子进程与父进程对他们的修改互不影响。

对vfork:子进程修改变量对父进程是可见的(子进程先执行)

进程退出
  • 不管是那种退出方式,最终都会***执行内核中的同一段代码***。这段代码用来关闭进程所有已打开的文件描述符,释放它所占用的内存和其他资源。

  • 在main函数中执行return调用about函数
    调用exit函数进程收到某个信号,而该信号使程序终止
    调用_exit函数
    在main函数中执行return
  • 比较:

    方式特点
    exit1.是一个函数,有参数 2.控制权交给系统 3.exit(0):正常退出 exit(1):执行过程中有错误发生 eg:溢出,除数为0 4.头文件#incldue<stdlib.h> 5.exit()退出进程会清理I/O缓冲区
    return1.函数执行完后的返回 2.控制权交给调用函数
    _exit1.是一个函数 2.头文件#include<unistd.h> 3._exit()退出直接结束到内核中
  • eg:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    printf("ok!\n");
    printf("yes");
    exit(0);
}

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
int main(void)
{
    printf("ok!\n");
    printf("yes");
    _exit(0);
}

在这里插入图片描述

原因:printf函数使用的是***缓冲I/O***的方式,在遇到‘\n’换行符时自动从缓冲区中将记录读出来。

exit是将缓冲区的数据读出来后才能退出,所以调用exit函数后程序并不会马上退出,所以就会导致出现僵尸程序。

_exit是直接退出进入到内核中去


执行新程序
  • 使用fork或vfork创建子进程后,子进程通常会调用exec函数来执行另一个程序,系统调用exec用于执行一个可执行程序以代替当前进程的执行映像

  • exec调用并没有生成新程序,一个进程一旦调用exec函数,ta本身就’死亡了‘,系统把代码段替换成新的程序的的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段和堆栈段,唯一保留的就是进程ID。也就是说,还是同一个进程,不过执行的是另一个程序。
  • linux下,exec函数族有6种不同的调用形式,头文件#include<unistd.h>

#include<unistd.h>
int execve(const char *path,char * const argv[],char *const envp[]);
int execv(const char *path,char * const envp[]);
int execle(const char * path,const char *arg,...);
int execl(const char *path,const char *arg,...);
int exevp(const char *file,char *const argv[]);
int execlp(const char *file,const char *arg,...);
  • 环境变量:系统预定义的参数。定义了用户的工作环境

    eg:在shell中执行命令,你只要打入命令名,不用打路径。是因为系统在环境变量中,将变量搜索的路径全列在环境变量中。

  • env命令可查看环境变量值,用户也可以修改这些变量值以定制自己的工作环境。

#include<stdio.h>
#include<malloc.h>
extern char **environ;
int main(int argc,char *argv[])
{
    int i;
    printf("Argument:\n");
    for(i=0;i<argc;i++)
        printf("argc[%d] is %s\n",i,argv[1]);
    printf("Environment:\n");
    for(i=0;environ[i]!=NULL;i++)
        printf("%s\n",environ[i]);
    return 0;
}

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

在这里插入图片描述

在这里插入图片描述

而是用env命令执行的结果如下:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

所以结果都是一样的

  • environ 是系统与定义的全局变量,用来显示各个环境变量的值
  • 类型是char **,存储着系统的环境变量

  • 下面介绍个exec函数是如何将main函数需要的参数传递给ta的:

  • argv和envp两个参数的大小都是受限制的,Linux操作系统通过宏ARG_MAX来限制他们的大小,如果他们的容量之和超过ARG_MAX定义的值,将会发生错误。这个宏定义在<linux/limits.h>头文件中

    exev函数:execv函数通过路径名方式调用可执行文件作为新的进程映像。他的argv参数用来提供给main函数的argv参数。argv参数是一个以NULL结尾(最后一个元素必须是空指针)的字符串数组
    execve函数:参数path是将要执行的程序的路径名,参数argv,envp与main函数的argv,envp对应
    execl函数:与execv函数用法相似,只是在传递argv参数的时候,每个命令行参数都声明一个单独的参数,注意这些参数要以一个空指针作为结束
    execle函数:与execl函数用法相似,只是要显示指定环境变量。环境变量位于命令行参数最后一个参数的后面,即空指针后面,也就是位于空指针之后。
    5.execvp函数:与execv用法一致,不同的是参数filename。该函数如果包含'/'就相当于路径名;如贵哦不包含,函数就到PATH环境变量定统一的目录中去找可执行文件。
    execlp函数:类似于execl函数,他们的区别和execvp与execv的区别是一样的。
    

    只有execve是系统调用,其他都是库函数,他们实现时都调用excve函数。

    • 正常情况下这些函数是不会返回的,因为进程的执行映像已经被替换,没有接受返回值的地方了。如果错误(文件名和参数错误),返回-1。
    • exec函数错误表
    errno错误描述
    EACCES指向的文件或脚本文件没有设置可执行位,即指定的文件是不可执行的
    E2BIG新程序的命令行参数与环境容量之和超过ARG_MAX
    ENOEXEC由于没有正确的格式,指定的文件无法正常执行
    ENOMEM没有足够的内存空间执行指定的程序
    ETXTBUSY指定的文件被一个或多个进程以可写的方式打开
    EIO从文件系统读入文件时发生I/O操作
    • exec函数族可以执行二进制可执行文件,也可以执行shell脚本程序,但是shell脚本必须以下面的格式开头:#!interpretername [arg]
      • interprewtername:shell或其他解释器。eg:/bin/sh /usr/bin/perl
      • arg:是传递给解释器的参数
  • 演示exec函数的用法:

execve.c
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(int argc,char *argv[],char **environ)
{
    pid_t pid;
    int stat_val;
    printf("exec example!\n");
    pid=fork();
    switch(pid)
    {
        case -1:
        {
            perror("process creation failed\n");
            exit(1);
        }
        case 0:
        {
            printf("child process is running\n");
            printf("My pid=%d,parentpid=%d\n",getpid(),getppid());
            printf("uid=%d,gid=%d\n",getuid(),getgid());
            execve("processimage",argv,environ);
            printf("process never go to here!\n");
            exit(0);
        }
        default:
        {
            printf("parent process is running\n");
            break;
        }
    }
    wait(&stat_val);
    exit(0);
}
processimage.c
/*用来替换进程映像的程序*/
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main(int argc,char *argv[],char **environ)
{
    int i;
    printf("i am a process image!\n");
    printf("my pid=%d,parentpid=%d\n",getpid(),getppid());
    printf("uid=%d,gid=%d\n",getuid(),getpid());
    for(i=0;i<argc;i++)
    {
        printf("argv[%d]:%s\n",i,argv[i]);
    }
}
编译程序:gcc -o processimage processimage.c   gcc -o execve execve.c 
  • 从执行结果可以看出:执行新程序的进程保持了原来进程的进程ID,父进程ID,实际用户ID,实际组ID,同时我们还可以看到当调用新的可执行程序后,原有的子进程的映像被替代,不再被执行。所以子进程无法执行到printf(“process never go to here!\n”);因为子进程已经被新的执行映像所替代。

  • 执行新程序的进程保持了原有程序的:

    原来的进程ID,父进程ID,实际用户ID,实际组ID
    当前工作目录
    根目录
    创建文件时使用的屏蔽字
    进程信号屏蔽字
    未决警告
    和进程相关的使用处理器的时间
    控制终端
    文件锁
    等待进程结束(wait和waitpid)
    • 函数原型:
    #include<sys/types.h>
    #include<sys/wait.h>
    pid_t wait(int *statloc);
    pid_t waitpid(pid_t pid,int *statloc,int options);
    
    • wait和waitpid所返回的终止状态的宏

    头文件sys/wait.h中定义了解读进程退出状态的宏

    宏定义说明
    WIFEXITED(stat_val)如果子进程是正常结束的,该宏返回一个非0值,表示真。若异常结束,返回0,表示假
    WEXITSTTUS(stat_val)若WIFEXITED返回值非0,它返回子进程中exit或_exit参数的低八位
    WIFSIGNALED(stat_val)若子进程异常终止,它就取得一个非0值,表示真
    WTERMSIG(stat_val)如果宏WIFSIGNALED的值非0,该宏返回使子进程异常终止的信号编号
    WIFSTOPPED(stat_val)若子进程暂停,它就取得一个非0值,表示真
    WSTOPSIG(stst_val)若WIFSTOPPED非0,它返回使子进程暂停的信号编号
    • wait:

      • 作用:
        • 父进程暂停执行,直到它的一个子进程结束为止
      • 返回值:
        • 终止运行的子进程的PID
      • 参数statloc:指向的变量存放子进程的退出码,即从子进程的main函数返回的值或者子进程中exit函数的参数。如果statloc不是一个空指针,状态信息将被写入他指向的变量。
    • waitpid:

      • 作用:也用来等待子进程的结束,用来等待某个特定进程结束。

      • 参数:

        • pid:指明要等待的子进程的PID,pid的意义接下表。
        取值意义
        pid>0等待其进程ID等于pid的子进程退出
        pid=0等待其组ID等于调用进程的组ID的任一子进程
        pid<-1等待其组ID等于pid绝对值的任一子进程
        pid=-1等待任一组进程
        • statloc:与wait函数中的参数一样
        • options:允许用户改变waitpid的行为,若将该参数赋值为WNOHANG,则使父进程不被挂起而立即返回并执行其后的代码。
      • 如果想让父进程周期性的检查某个特定的子进程是否已经退出,可以:

      waitpid(child_pid,(int *)0,WNOHANG);
      

      如果子进程尚未退出,它将返回0

      如果子进程已经结束,返回child_pid

      调用失败返回-1(没有孩子进程,参数不合法等)

    • wait等待第一个终止的子进程。而waitpid则可以指定等待特定的子进程。waitpid提供了一个wait的非阻塞版本。有时希望取得一个子进程的状态,但不想使父进程阻塞,waitpid提供了一个这样的选项:WNOHANG,它可以使调用者不阻塞,如果没有任何一个子进程的进程调用wait函数,他会立即出错返回。

获得进程ID(getpid)
  • 原型:
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void);
setuid和setgid
  • 作用:

    • setuid:设置实际用户ID和有效用户ID
    • setgid:设置实际组ID和有效组ID
  • 原型:

#include<sys/types.h>
#include<unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
  • 设置用户ID的setuid函数必须遵守以下规则(设置组ID的setgid函数一样):
    • 若进程有root权限,则函数将实际用户ID,有效用户ID设置为参数uid。
    • 若进程不具有root权限,但uid等于实际用户ID,则setuid只将有效用户ID设置为uid,不改变实际用户ID。
    • 若以上两条都不能满足,则函数调用失败,返回-1,并设置errno为EPERM
  • 说明:
    • 只有超级用户进程才可以改变实际用户ID。所以一个非特权用户进程是不能通过setuid或seteuid得到特权用户权限的。但是su命令能使一个普通用户变成特权用户。
    • 因为su是一个“set_uid"程序。执行一个设置了set_uid位的程序时,内核将进程的有效用户ID设置为文件属主的ID。而内核检查一个进程是否有权限访问某文件的权限时,是使用进程的有效用户ID来进行检查的。su程序的文件属主是root,普通用户寻星su命令时,su进程的权限是root权限。
    • 当进程的有效用户ID即euid是root用户时,如果调用setuid函数使euid为其他非root用户,则该进程从此进步具有超级用户的权限了。
  • 说明:
    • 可以这样使用setuid函数:开始时某个程序需要root权限完成一些工作,但后续的工作不需要root权限,可以将该程序执行文件设置为set_uid,并使得该文件的属主是root。这样普通用户执行这个程序时,进程就具有root权限了。当不再需要root权限时,调用setuid(getuid())恢复进程的实际用户ID和有效用户ID为执行该程序的普通用户的ID。对于一些提供网络服务的程序,这样做是非常有必要的。
改变进程的优先级
  • nice

    • 通过系统调用nice可以改变进程的优先级。
    • 函数原型:
    #include<unistd.h>
    int nice(int increment);
    
    • getpriority:

      • 函数声明:
      #include<sys/resource.h>
      int getpriority(int which,int who);
      
      • 作用:该函数返回一组进程的优先级。

      • 参数:which和who确定返回哪一组进程的优先级。

        • which的取值以及who的对应含义

        • 函数如果调用成功返回指定进程的优先级,出错返回-1,并设置errno的值

          PRIO_PROCESS:一个特定的进程,此时who的取值为进程ID
          PRIO_PGRP:一个进程组的所有进程,who:进程组ID
          PRIO_USER:一个用户拥有的所有进程,who:实际用户ID
          
        • errno:

        errno含义
        ESRCHwhich和who的组合与现存的所有进程都不匹配
        EINVALwhich是一个无效值
      • 注意:当指定的一组进程的优先级不同时,getpriority将返回其中优先级最低的一个。当它返回-1时,可能是发生错误,也有可能是返回的是指定进程的优先级。区分它们的唯一方法是:在调用前将errno清零。如果函数返回-1,并且errno不为0,说明有错误产生。

    • setpriority:

      • 原型:
      #include<sys/resource.h>
      int setpriority(int which,int who,int prio);
      
      • 作用:该函数用来设置指定进程的优先级。进程指定的方式与上述相同。
      • 调用成功返回指定进程的优先级,出错返回-1,并设置相应的errno。除了上面的errno的可能值外,还有下面的:
      errno含义
      EPERM要设置优先级的进程与当前进程不属于同一个用户,并且当前进程没有CAP_SYS_NICE特许
      EACCES该调用可能降低进程的优先级并且没有CAP_SYS_NICE特许
    • 通过以上两个函数完全可以改变进程的优先级。nice系统调用是他们的一种组合形式,nice系统调用等价于:

    int nice(int increment)
    {
        int oldprogetpriority(PRIO_PROCESS,getpid());
       return setpriority(PRIO_PROCESS,getpid(),oldpro(increment)); 
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值