Linux系统下的进程控制


    Linux 系统是多任务操作系统,可同时进行多个程序完成多项工作。
    进程是处于活动状态的程序,在操作系统的管理下,所有进程共享计算机中的硬件资源。进程作为系统运行时的基本逻辑成员,不仅作为独立个体运行在系统上,而且还将相互竞争系统资源。
    程序是一个包含可以执行代码的文件,是一个静态的文件。

进程的概念:
    进程是一个开始执行但还没有结束的程序的实例,就是可执行文件的具体实现,是一个动态的概念。
    Linux 系统上所有运行的东西都可以称为一个进程。
    当程序被系统调用到内存以后,系统会给程序分配一定的资源(内存、设备等),然后进行一系列的复杂操作,使程序变成进程以供系统调用。
    系统给每一个进程分配一个 ID 号以便于识别。
    在一个 CPU 上,可以存在多个进程;在同一时间内,一个 CPU 只能有一个进程工作。操作系统通过一定的调度算法管理算有进程。
    Linux 系统至少有一个进程。一个程序可以对应多个进程,一个进程只能对应一个程序。
    对操作系统来说,进程是程序的一次执行过程和资源分配的基本单位
    一个进程不仅包含了正在运行的代码,也包括了运行代码所需要的资源(包括用户用到的资源和操作系统需要用的资源)。操作系统通过 PCB (进程控制块 )的数据结构管理进程。


进程的分类:
    进程具有运行、阻塞和就绪 三种基本状态


     进程大致上来讲可分为两大类: 系统进程与用户进程 (守护进程)


进程的基本属性:

    进程号( PID) :操作系统通过该号标示一个 用户进程。

    父进程号(PPID):除了init进程外,所有的进程都是通过init进程创建的。进程又可以创建其他进程,形成一个倒过来的树形结构,每个进程都有自己的父进程。

    进程组号( PGID ):操作系统允许对进程分组,不同的进程通过进程组号标示。
    真实用户号( UID ):用户唯一的标识号,用于标识一个用户。
    真实组号( GID ):用户组的唯一标识号,用于标识一个用户组。

进程管理相关命令:
1.ps
¨ 进程查看命令
¨ 语法格式如下:
¨ ps   [选项]
¨ 常用选项含义如下:
-e 显示所有进程。
-f :全格式。
-h: 不显示标题。
-l: 长格式
-w: 宽输出
a: 显示终端上的所有进程,包括其他用户的进程
r: 只显示正在运行的进程
x: 显示没有控制终端的进程
u: 使用用户格式输出

2.top
¨ 显示系统当前的进程和其他状况
¨ 命令语法格式如下:
¨ top [- dqsiupSc ] [-dcount] [-s time] [-u username]
¨ 常用选项含义如下:
d 指定每两次屏幕信息刷新之间的时间间隔。
q 表示没有任何延迟地进行刷新。
s 表示安全模式下运行。
3. renice
¨ renice 命令允许用户修改一个正在运行进程的优先权。利用 renice 命令可以在命令执行时调整其优先权。其命令语法格式如下:
¨ renice   -number PID
¨ 其中,参数 number 表示优先级别号。示例:

4. wait
¨ wait 命令将实现对一个进程的等待。命令格式为:
¨ wait  [n]
¨ 等待进程号为 n 的一个进程的完成并将报告进程的终止状态。没有参数,则将等待所有后台进程的完成并返回代码 0
¨ 示例:等待进程号为 13199 的进程结束。

6. kill
¨ 当用户需要中断一个前台进程的时候,通常是使用 < Ctrl+c > 组合键;
¨ 对于一个后台进程须求助于 kill 命令
¨ kill 命令的语法格式很简单,大致有以下两种方式:
¨ kill  [-s 信号| -p ] [ -a ] 进程号
¨ kill  -l [信号]


进程状态和状态转换:

    进程在生存周期中呈现出各种状态及状态的转换,这些信息反映了进程获取系统资源的情况。Linux系统的进程状态模型如表所示。

    (1).子进程被Linux内核调入CPU执行的过程:

     进程的生命周期包括从创建到退出的全部状态转化,它的生命周期里并不一定要经历所有的状态。父进程创建子进程,子进程被
Linux 内核调入 CPU 执行的过程可用跨职能流程图来反映,如后图所示。
   
    最初,父进程通过 fork 系统调用创建子进程,子进程被创建后,处于创建状态。 Linux 内核为子进程配置数据结构,如果内存空间足够,子进程在内核中就绪,否则在 Swap 分区就绪。这时子进程处于就绪状态,等待 Linux 内核调度。Linux 内核会为子进程分配 CPU 时钟周期,在合适的时间将子进程调度上 CPU 运行,这时子进程处于内核状态,子进程开始运行。被分配的 CPU 时钟周期结束时, Linux 内核再次调度子进程,将子进程调出 CPU ,子进程进入用户状态。待子进程被分配的下一个 CPU 时钟周期到来时, Linux 内核又将子进程调度到 CPU 运行,使子进程进入内核状态。如果有其它的进程获得更高优先级,子进程的时钟周期可能会被抢占,这时又会回到用户状态。

   (2).子进程进入睡眠状态

   子进程在运行时,如果请求的资源得不到满足将进入睡眠状态,睡眠状态的子进程被从内存调换到 Swap 分区。被请求的资源可能是一个文件,也可能是打印机等硬件设备。如果该资源被释放,子进程将被调入内存,继续以系统状态执行,如图所示。

  (3).子进程结束

    子进程可以通过 exit 系统调用结束,这时子进程将进入到僵死状态,生命周期结束,如图所示。子进程在内核中的数据结构又被称为上下文。上下文包括 3 个部分,用户级上下文是子进程用户空间的内容,寄存器上下文是子进程运行时装入 CPU 寄存器的内容,系统级上下文是子进程在 Linux 内核中的数据结构。


    子进程切换时,CPU收到一个软中断,这时上下文将被保存起来,称之为保存现场。子进程再次运行时,上下文被还原到相关位置,称之为还原现场。整个过程称为上下文切换,保存上下文的数据空间称为u区,是Linux内核为进程分配的存储空间。
   内核在以下情况会进行上下文切换操作:
       子进程进入睡眠状态时。
       子进程时钟周期结束,被转为用户状态时。
       子进程再次被调度上 CPU 运行,转为系统状态时。
       子进程僵死时。

进程的创建:
     在Linux系统中,用户创建子进程的唯一方法就是使用fork系统调用。fork系统调用的流程如图所示。


    1.fork创建进程并系统调用
    格式:

    #include <unistd.h>

    pid_t fork(void);

    功能:创建子进程

   fork的奇妙之处在于它被调用一次,却返回两次,它可能有三种不同的返回值:

   (1).在父进程中,fork返回新创建的子进程的PID

   (2).在子进程中,fork返回0

   (3).如果出现错误,fork返回一个负值


    Linux系统通过fork()系统调用创建一个进程,fork()函数定义如下:

    #include <sys/ types.h >
    #include <unistd.h>
    pid_t fork(void);

    fork( ) 函数的作用是创建一个新的进程。在应用程序调用 fork( ) 函数后,会创建一个新的进程,称为子进程,原来的进程叫做父进程。从此,运行的已经是两个进程了。子进程和父进程都可以得到 fork 函数的返回值。子进程中, fork 返回 0 ;父进程中, fork 返回子进程的进程号。若创建失败,返回 -1。

例程如下:
#include <sys/types.h>
#include <unistd.h>
main()
{
pid_t pid;
/*此时仅有一个进程*/
pid=fork();
/*此时已经有两个进程在同时运行*/
if(pid<0)
printf("error in fork!");
else if(pid==0)
printf("I am the child process, ID is %d\n",getpid());
else
printf("I am the parent process,ID is %d\n",getpid());
}

    在pid=fork()之前,只有一个进程在执行,但在这条语句执行之后,就变成两个进程在执行了,这两个进程的共享代码段,将要执行的下一条语句都是if(pid==0)。两个进程中,原来就存在的那个进程被称作“父进程”,新出现的那个进程被称作“子进程”,父子进程的区别在于进程标识符(PID)不同

   2.vfork创建进程

   #include <sys/types.h>

   #include <unistd.h>

   pid_t vfork(void)

   功能:创建子进程。

   区别:

   (1). fork:子进程拷贝父进程的数据段

    vfork:子进程与父进程共享数据段

  (2). fork:父、子进程的执行次序不确定

    vfork:子进程先运行,父进程后运行

例程如下:
# include <unistd.h>
# include <stdio.h>
int main(void)
{
 pid_t pid;
 int count=0;
 pid = vfork();
 count++;
 printf( “count = %d\n", count );
 return 0;
}

    3.exec函数
    exec 用被执行的程序替换调用它的程序。

    区别:

       fork创建一个新的进程,产生一个新的PID

       exec启动一个新程序,替换原有的进程,因此进程的PID不会改变。
  

   格式:

#include <unistd.h>

extern char **environ;

intexecve(const char*filename, char *const argv[], char *const envp[]);

intexecl(const char *path,const char *arg,...);

intexeclp(const char *file,const char *arg,...);

intexecle(const char *path,const char *arg,..., char * const envp[]);

intexecv(const char *path,char *const argv[]);

intexecvp(const char *file,char *const argv[]);


说明:
    上面统称为 exec 函数系列,只有 execve 是真正的系统调用,其它五个函数最终都调用 execve
    fork 创建子进程后,子进程执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用 exec 函数系列来执行另一个程序。当进程调用 exec 函数系列时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
    参数path是被执行程序的完整路径名;argvenvp是传给被执行程序的命令行参数和环境变量;file是文件名,有相应函数自动到环境变量PATH给定的目录中寻找。

例程如下:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char *argv[])
{
    /*判断入参有没有传入文件名*/
	if(argc<2)
	{
	    perror("you haven,t input the filename,please try again!\n");
		exit(EXIT_FAILURE);
	
	}
	/*调用execl函数,用可执行程序file_creat替换本进程*/
	if(execl("./file_creat","file_creat",argv[1],NULL)<0)
		perror("execl error!");

}

进程的基本操作:
   
    进程的基本操作包括 fork 调用、 exec 调用、 exit 调用、 wait 调用和 sleep 调用,相关函数被定义在系统调用库 unistd.h 中。
  
    ( 1) .进程终止系统调用(exit函数)
    格式:

    #include <stdlib.h>

    void exit(int status);

    说明:

    exit()自我终止当前进程,使其进入僵死状态,等待父进程进行善后处理,该调用会尽可能释放当前进程占用的资源。status是返回给父进程的一个整数。


例程:
#include<stdio.h>
#include<stdlib.h>
main()
{
   printf("This process will exit!\n");
   exit(0);
   printf("Never be displayed!\n");
}

    系统调用 exit 的功能是终止发出调用的进程,它包含两个函数,分别是 _exit( ) 函数和 exit( ) 函数。它们的一般形式如下:
    void _exit( int status);
    void exit( int status);

     系统调用_exit()立即终止发出调用的进程。所有属于该进程的文件描述符都关闭。如果该进程拥有子进程,那么父子进程关系被转到init进程上。被结束的进程将收到来自子进程的僵死信号SIGCHLD。如果被结束的进程在控制台或终端上运行,shell程序将收到SIGHUP信号。

     函数中的参数status是返回给父进程的状态值,父进程可通过wait系统调用获得。status只有最低1个字节能被父进程读取,由此可知,实际值域范围为0255

     系统调用_exit()没有返回值,被终止进程不会知道该调用是否成功。另外,该调用不会刷新输入输出缓冲区,因此进程结束前必须自己刷新缓冲区,或者改用exit()系统调用。exit()系统调用将进行一些上下文清理工作,例如释放所有占用的资源、清空缓冲区等。


    (2).进程等待系统调用 

    格式:

    #include <sys/types.h>

    #include <sys/wait.h>

    pid_t wait(int *status);

    pid_twaitpid(pid_tpid, int *status, int options);


    说明: wait ()等待任一僵死的子进程,将子进程的退出状态(退出值、返回码、返回值)保存在参数 status 中。即进程一旦调用了 wait ,就立即阻塞自己,由 wait 分析是否当前进程的某个子进程已经退出,如果找到这样一个已经变成僵尸的子进程, wait 就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程, wait 就会一直阻塞在这里,直到有一个出现为止。若成功,返回该终止进程的 PID ;否则返回 -1

    

    waitpid ()等待标识符为 pid 的子进程退出,将该子进程的退出状态(退出值、返回码、返回值)保存在参数 status 中。参数 options 规定调用的行为, WNOHANG 表示如果没有子进程退出,立即返回 0 WUNTRACED 表示返回一个已经停止但尚未退出的子进程信息。


例程如下:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
   pid_t pid;
   pid = fork();
if (pid < 0) 
   {
     perror("fork failed");
     exit(1);
    }
   if (pid == 0)
   {
     int i;
     for (i = 3; i > 0; i--) 
     {
         printf("This is the child\n");
         sleep(1);
       }                
      exit(3);
     } 

else 
    {
      int stat_val;
      waitpid(pid, &stat_val, 0);
      if (WIFEXITED(stat_val))   
      printf("Child exited with code %d\n", WEXITSTATUS(stat_val\
));
      }
    return 0;
}

   从运行结果分析程序的运行过程如下所示。
¨ 1 )父进程初始化。
¨ 2 )父进程调用 fork fork 为系统调用,因此进入内核。如果 fork 不成功,系统显示“ fork failed” ,如果成功,内核父进程复制出一个子进程。本示例是 fork 成功,所以现有父子两个一模一样进程。但是父进程先返回还是子进程先返回,取决于内核的调度算法。
¨ 3 )如果子进程先返回,返回值 pid 0 ,执行 if ( pid == 0) 后的语句,显示三次字符串“ This is the child” exit 3 )表示退出 status 3

 (4)如果是父进程先返回,返回值pid是子进程的id号,一个大于0的数,执行if (pid == 0) else后的语句。waitpid(pid, &stat_val, 0)会阻塞父进程等待pid指定的子进程退出,此时CPU空闲,系统会调度子进程执行,显示三次字符串“This is the child”, 退出status3,子进程结束后唤醒父进程,显示Child exited with code 3


    wait函数系统调用的宏定义:


    (3).系统睡眠函数(sleep函数)
    系统调用 sleep 用来使进程主动进入睡眠状态,该函数的一般形式是:
    sleep( 秒数 );
    执行该系统调用后,进程将进入睡眠状态,直到指定的秒数已到。正常情况下,该调用的返回值为 0 ,若是因为被信号所唤醒,则返回值为原始秒数减去已睡眠秒数的差。

   
    (4).获得指定进程的标识符(getpid函数、getppid函数)

    格式:

    #include <sys/types.h>

   #include <unistd.h>

   pid_tgetpid(void);

   pid_tgetppid(void);

  说明:getpid()返回调用该系统调用的进程的id号,getppid()返回调用该系统调用的进程父进程的id号。

   例程:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
  printf("Current process ID:%d\n",(int)getpid());
  printf("Parent process ID:%d \n",(int)getppid());
  return 0;
}

    (5).设置进程的信息(setuid函数、setgid函数、setpgrp函数和setpgid函数)
    设置进程的 UID 可使用 setuid () 函数,设置进程的 GID 可使用 setgid () 函数。这两个调用的一般形式如下:
    int setuid ( uid_t uid );
    int setgid ( gid_t gid );
    setuid () 函数可修改发出调用进程的 UID ,参数 uid 为创建进程的用户信息。如果以普通用户的 UID 作为参数执行该调用, Linux 内核将直接设置进程 UID 为参数 uid 信息。如果以根用户的 UID 作为参数,为了保障系统的安全性, Linux 内核将以进程表和 u 区中用户真实的标识号来设置进程 UID setuid () 函数执行成功时,返回值为 0 ,否则返回 –1
    setgid () 函数可修改发出调用进程的 GID ,与前者不同,该调用不会检验用户的真实身份。参数 gid 为进程的新 GID 信息。执行成功时,返回值为 0 ,否则返回 –1

    系统调用 setpgrp () setpgid () 都是用来设置进程 PGID ,它们的一般形式为:
    int setpgrp (void);
    int setpgid ( pid_t pid , pid_t pgid );
    其中, setpgrp () 函数直接将进程的 PGID 设为与 PID 相同的数值, setpgid () 以其中参数修改 PGID 。参数 pid 为指定进程的 PID ,值为 0 时修改发出调用进程的 PGID 。参数 pgid 为指定的 PGID 信息,值为 0 时,修改所有 PID 与参数 pid 相等的进程,将这些进程的 PGID 值设为参数 pgid 的值。若以普通用户权限发出此调用,而 PGID 原本为根用户组所有,那么只有在指定进程与调用进程的 EUID 相同时,或者指定进程为调用进程的子进程时才有效。

    (6).修改进程的工作目录(chdir函数)
    文件操作部分曾介绍过 chdir () 系统调用,该调用对于进程控制有不同的意义。 chdir () 函数将进程的当前工作目录改为由参数指定的目录。该调用的一般形式如下:
int chdir (const char * path);
    参数 path 为指定目录的路径,发出该调用的进程必须具备该目录的执行权限。调用成功时返回值为 0 ,否则返回 –1 ,并设置相应的错误代码。

    (7).根交换操作(chroot函数)
    系统调用 chroot 又被称为根交换操作,作用通常是在一个 Linux 系统上虚拟另一个 Linux 系统,根交换后,所有的命令操作都被重新定向。该调用的一般形式如下:
    int chroot (const char * path);
    参数 path 为新的根目录路径,执行后,进程将以该目录作为根目录,并且使进程不能访问该目录以外的内容。该操作不改变当前工作目录,如果当前工作目录在指定目录以外,则无法访问其中内容。根交换操作只能由根用户发出,调用成功时返回值为 0 ,错误时返回 –1 ,并设置相应的错误代码。

    (8).修改进程的优先级(nice函数)
    系统调用 nice() 用来改变进程的优先级。该调用的一般形式如下:
    int nice( int inc);
    参数 inc 为调用 nice() 函数的进程优先级数值的增量。优先级数值越低的值,被调度上 CPU 运行的机会越大;优先级数值越高,被调度上 CPU 运行的机会越低。但是,只有根用户能为 inc 参数设置负值,使进程优先级提高,普通用户设置的正值会降低优先级。当调用成功时,返回值为 0 ,否则返回 –1







  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

daijingxin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值