操作系统实验2---进程管理(更完..)

    在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID(>0)。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

1.进程的创建
编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符;子进程显示字符“a”,父进程分别显示字符“b”和“c”。试观察记录屏幕上的显示结果,并分析原因。

#include<stdio.h>
#include<unistd.h>
int main()
{
    int p1,p2;
    if(p1=fork())               //p1进程的父进程输出b
        putchar('b');
    else
    { 
        if(p2=fork())           
          putchar('c');        //p2进程的父进程输出c
        else putchar('a');     //p2进程的子进程输出a
    }
}

p1进程fork之后,p1变为父进程,生成一个p1的子进程,此时作为父进程的p1返回的不是0,好,输出b,此时作为p1的子进程p儿子1由于是返回的0所以进入else判断区域,此时遇到了p2进程,同样上面操作,在p儿子1手下{p2作为了父亲输出了c,p2儿子输出了a}但是是由于两个进程就是独立个体,各自运行,互不干扰,父子进程谁先执行不由fork决定,而是由系统当前环境和进程调度算法决定,因此根据个人系统情况不同,打出上述所有的顺序都不一定。

2.进程的控制 <任务> 修改已编写好的程序,将每个程序的输出由单个字符改为一句话,再观察程序执行时屏幕上出现的现象,并分析其原因。如果在程序中使用系统调用lockf()来给每个程序加锁,可以实现进程之间的互斥,观察并分析出现的现象。

#include<stdio.h>
#include<unistd.h>
int main()
{
    int p1,p2,i;
    if(p1=fork())
         for(i=0;i<500;i++)
                printf("child %d\n",i);
    else
    { 
     if(p2=fork())
            for(i=0;i<500;i++) 
            printf("son %d\n",i);
     else
            for(i=0;i<500;i++)  
            printf("daughter %d\n",i);
    }
}

输出的过程是不会被打断的,能完整输出一条语句,但是每条语句之间的顺序还是不确定的

child….
son…
daughter…
daughter…
或child
…son
…child
…son
…daughter
分析:由于函数printf()输出的字符串之间不会被中断,因此,字符串内部的字符顺序输出时不变。但是 , 由于进程并发执行时的调度顺序和父子进程的抢占处理机问题,输出字符串的顺序和先后随着执行的不同而发生变化。这与打印单字符的结果相同。
#include<stdio.h>
#include<unistd.h>
int main()
{
    int p1,p2,i;
    if(p1=fork())
    {
         lockf(1,1,0);
         for(i=0;i<500;i++)  printf("child %d\n",i);
         lockf(1,0,0);
    }
    else
     {
          if(p2=fork())
          { 
            lockf(1,1,0);
               for(i=0;i<500;i++)  printf("son %d\n",i);
            lockf(1,0,0);
          }
          else
         { 
             lockf(1,1,0);
                for(i=0;i<500;i++)  printf("daughter %d\n",i);
             lockf(1,0,0);
         }
     }
}

 

lockf(1,1,0)是锁定屏幕输出,不让其他进程可以输出到屏幕,lockf(1,0,0)则是解锁,使得每一个for循环都能完整执行并输出,但是三个输出的顺序依然不定。大致与未上锁的输出结果相同,也是随着执行时间不同,输出结果的顺序有所不同。 分析:因为上述程序执行时,不同进程之间不存在共享临界资源(其中打印机的互斥性已有由操作系统保证)问题,所以,加锁与不加锁效果相同。

3.软中断通信 〈任务1〉 编制一段程序,使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按ctrl+c键),当捕捉到中断信号后,父进程用系统调用kill()向两个子进程发出信号,子进程捕捉到信号后,分别输出下列信息后终止: child process1 is killed by parent! child process2 is killed by parent! 父进程等待两个子进程终止后,输出以下信息后终止: parent process is killed!

<程序流程图> 图片描述

SIGINT信号:程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。接收默认处理:接收默认处理的进程通常会导致进程本身消亡。例如连接到终端的进程,用户按下CTRL+c,将导致内核向进程发送一个SIGINT的信号,进程如果不对该信号做特殊的处理,系统将采用默认的方式处理该信号,即终止进程的执行。

#include<stdio.h>
#include<signal.h>
#include<unistd.h> 
#include<stdlib.h>
#include<wait.h>
void waiting(), stop(),alarming();
int wait_mark;

int main()
{
	int p1, p2;
	if (p1 = fork())             /*创建子进程p1*/
	{//子进程1创建成功,当前运行的是子进程1的父进程
		if (p2 = fork())            /*创建子进程p2*/
		{//子进程2创建成功,当前运行的是子进程2的父进程
			wait_mark = 1;
			signal(SIGINT, stop);       //不到5s就捕获CTRL+C信号,执行stop操作
                        signal(SIGALRM,alarming);   //接收到SIGALRM信号,执行alarming操作
			waiting();                 //如果捕捉到此时wait_mark=0不等待
			kill(p1, 16);              //向p1进程发16中断发生的信号
			wait(0);                   //阻塞父进程等待中断16发生后终止子进程1
			kill(p2, 17);              //向p1进程发17中断发生的信号
			wait(0);                   //阻塞父进程等待中断17发生后终止子进程2
                        printf("parent process is killed!\n");
			exit(0);                 //终止父进程
		}
		else
		{//当前是在子进程2创建成功的条件下运行的是子进程的2的子进程
		        wait_mark = 1;
                        signal(17,stop);
                        signal(SIGINT,SIG_IGN);
                        while(wait_mark!=0);
			lockf(1, 1, 0);
			printf("child process2  is killed by parent!\n");
			lockf(1, 0, 0);
			exit(0);
		}
	}
	else
	{//当前是在子进程1创建成功的条件下运行的是子进程1的子进程
		wait_mark = 1;
                signal(16,stop);                 //接收到中断16发生的信号,置wait_mark为0
                signal(SIGINT,SIG_IGN);          //CTRL+C信号发生时,都会接收到信号,会把子进程给结束,所以要屏蔽掉。
		while(wait_mark!=0);            //wait_mark不为0会锁死在这,这就是5s内都不干程序也什么都不干的原因
		lockf(1, 1, 0);
		printf("child process1 is killed by parent!\n");
		lockf(1, 0, 0);
		exit(0);
	}
}

void waiting()
{
        sleep(5);                   //5s后唤醒
        if(wait_mark!=0)
           kill(getpid(),SIGALRM);  //如果5s之后检测wait_mark不是0,发出SIGALRM信号
}
void alarming()
{
        wait_mark=0;
}
void stop()
{
	wait_mark = 0;
}

〈任务2〉 在上面的任务1中,增加语句signal(SIGINT,SIG_IGN)和语句signal(SIGQUIT,SIG_IGN),观察执行结果,并分析原因。这里,signal(SIGINT,SIG_IGN)和signal(SIGQUIT,SIG_IGN)分别为忽略键信号以及忽略中断信号。

int kill(pid_t pid, intsignum);  功能:给指定进程发送信号。

注意:使用 kill() 函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者是超级用户。

参数:pid: 取值有 4 种情况 :pid > 0: 将信号传送给进程 ID 为 pid 的进程。

pid = 0: 将信号传送给当前进程所在进程组中的所有进程。

pid = -1: 将信号传送给系统内所有的进程。

pid < -1: 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。

signum: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母 ) 进行相应查看。

返回值:

成功:0

失败:-1

waitpid系统调用在Linux函数库中的原型是:

     #include <sys/types.h> /* 提供类型pid_t的定义 */

     #include <sys/wait.h>

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

从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下这两个参数:

pid:从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。

      pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
      pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
      pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
      pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

status如果不为空,会把状态信息写到它指向的位置,返回值:成功返回等待子进程的pid,失败返回-1

options:options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

     ret = waitpid(-1,  NULL,  WNOHANG | WUNTRACED);

如果我们不想使用它们,也可以把options设为0,如:

     ret = waitpid(-1,  NULL,  0);

如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

僵尸进程

当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止。

子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。

进程表中代表子进程的数据项是不会立刻释放的,虽然不再活跃了,可子进程还停留在系统里,因为它的退出码还需要保存起来以备父进程中后续的wait调用使用。它将称为一个“僵进程”。

如何避免僵尸进程

  1. 调用wait或者waitpid函数查询子进程退出状态,此方法父进程会被挂起。
  2. 如果不想让父进程挂起,可以在父进程中加入一条语句: signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的
  3. 注册信号处理函数,在信号处理函数总调用 wait 函数。

SIGCHLD 信号

当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止)

如果不想让子进程编程僵尸进程可在父进程中加入:signal(SIGCHLD,SIG_IGN);

如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。(父进程忽略了 SIGCHLD信号之后,会将僵尸子进程转交给init进程给处理,这样子就不保存父子关系了吗?)。

wait() 函数

#include <sys/types.h> 
#include <sys/wait.h>
pid_t wait(int *status);

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:

                    `pid = wait(NULL)`

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD

  1. wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。
  2. 返回的是子进程的PID,它通常是结束的子进程
  3. 状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit语句的退出码。
  4. 如果status不是一个空指针,状态信息将被写入它指向的位置
  5. waitpid() 函数

    waitpid() 是一个非常有用的函数,不单单可以等待子进程。 还可以等待进程组中的任意一个进程。 可以看到 wait() 函数实际上是 waitpid() 函数的特例。

    #include <sys/types.h> 
    #include <sys/wait.h>
    pid_t waitpid(pid_t pid, int *status, int options);
    参数:
    status:如果不是空,会把状态信息写到它指向的位置,与wait一样
    options:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起 (也就是不阻塞父进程)

    对于waitpid的p i d参数的解释与其值有关:
    pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。

    pid > 0 等待其进程I D与p i d相等的子进程。

    pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用者进程同在一个组的进程。

    pid < -1 等待其组I D等于p i d的绝对值的任一子进程

    wait与waitpid区别:

    在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
    waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
    实际上wait函数是waitpid函数的一个特例。waitpid(-1, &status, 0);

原文链接:

https://blog.csdn.net/wyhh_0101/article/details/83933308

必看!!!!绝了:https://www.cnblogs.com/Anker/p/3271773.html

https://www.cnblogs.com/lsgxeva/p/8051446.html

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
    int pid1,pid2;
    int EndFlag=0;
    int pf1=0;
    int pf2=0;

void IntDelete()
{
    kill(pid1,16);
    kill(pid2,17);
    EndFlag=1;
}

void Int1()
{
    printf("child process 1 is killed !by parent\n");
    exit(0);
}
void Int2()
{
    printf("child process 2 is killed !by parent\n");
    exit(0);
}

int main()
{
    int exitpid;
    signal(SIGINT,SIG_IGN);      //对产生的SIGINT信号进行屏蔽,按下CTRL+C没反应
    signal(SIGQUIT,SIG_IGN);     //对中断信号进行屏蔽
    if(pid1=fork())        
    {//进入pid1的父进程
      signal(SIGUSR1,Int1);      //用户自定义signal1中断信号,如果接受到执行Int1
      signal(SIGINT,SIG_IGN);    //屏蔽CTRL+C信号
      pause();
      exit(0);
    }
    else
        {//进入Pid1的子进程
         if(pid2=fork())
            {//进入pid2的父进程
              signal(SIGUSR1,Int2);     用户自定义signal2中断信号,如果接受到执行Int2
              signal(SIGINT,SIG_IGN);   //屏蔽所有信号
              pause();
              exit(0);
            }
        else
            {//进入pid2的子进程,此时pid1=0,pid2=0
              signal(SIGINT,IntDelete);   //调用kill()将信号传送给当前进程所在进程组中的所有进程
              waitpid(-1,&exitpid,0);    //阻塞式等待
              printf("parent process is killed\n");  //只能输出这一个,其他的全被屏蔽了
              exit(0);
            }
        }
}
  • 19
    点赞
  • 112
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.基本系统进程   Csrss.exe:这是子系统服务器进程,负责控制Windows创建或删除线程以及16位的虚拟DOS环境。   System Idle Process:这个进程是作为单线程运行在每个处理器上,并在系统不处理其它线程的时候分派处理器的时间。   Smss.exe:这是一个会话管理子系统,负责启动用户会话。   Services.exe:系统服务的管理工具。   Lsass.exe:本地的安全授权服务。   Explorer.exe:资源管理器。   Spoolsv.exe:管理缓冲区中的打印和传真作业。   Svchost.exe:这个进程要着重说明一下,有不少朋友都有这种错觉:若是在“任务管理器”中看到多个Svchost.exe在运行,就觉得是有病毒了。其实并不一定,系统启动的时候,Svchost.exe将检查注册表中的位置来创建需要加载的服务列表,如果多个Svchost.exe同时运行,则表明当前有多组服务处于活动状态;多个DLL文件正在调用它。   至于其它一些附加进程,大多为系统服务,是可以酌情结束运行的。由于其数量众多,我们在此也不便于一一列举。   在系统资源紧张的情况下,我们可以选择结束一些附加进程,以增加资源,起到优化系统的作用。在排除基本系统及附加进程后,新增的陌生进程就值得被大家怀疑了。 多内容请看Windows操作系统安装、系统优化大全、系统安全设置专题,或进入讨论组讨论。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值