【Linux系统】进程控制

时间:2024-05-10 21:56:17

再次理解进程

进程:内核的相关管理数据结构(task_struct(进程控制块PCB),mm_struct(地址空间),页表) + 代码和数据

那么如何理解进程具有独立性

我们之前已经学习过进程控制块啊,地址空间啊,页表啊,他们不都是随着进程的创建而被创建,所以每个进程都有独立的一份这三个结构,那代码是共享的,数据是经过写时拷贝实现进程之间的独立的。

fork函数的返回

fork子进程返回0

fork父进程返回是子进程的pid

为什么父进程返回的是子进程的PID,给子进程返回的是0!!!

首先我们为了父进程方便对子进程进行标识,进而进行管理。

退出码?退出信号?

我们在写c/c++main函数的时候我们是不是往往都会写一个return 0来结束程序的运行!

为什么?

我们在命令行中使用echo $?内键命令

会发现打印出我们刚刚通过./运行的程序的main函数的返回值,我们把return后面设置为什么就会为我们返回什么!

我们知道,内键命令打印的都是bash内部的变量数据。

所以父进程bash获取到的是,最近一个子进程退出的退出码

告诉关心方(父进程),我任务完成的怎么样了!

实际上0代表成功,非0代表失败。

这是程序的退出码

1、2、3……不同的非零值,一方面表示失败,一方面表示失败的原因,

它们都有对应的错误描述,可以以字符串的形式打印出来!

父进程bash为什么要得到子进程的退出码呢?

这是要知道子进程的退出情况(成功,失败,失败的原因是什么?)

这些不都是为了让我们用户看到吗,为我们用户负责!!!

退出码不仅可以使用默认的,也可以进行自定义!

进程的终止

1.进程的终止在做什么?

释放曾经的代码和数据所占用的空间

释放内核数据结构(task_struct)-->我们知道子进程结束,父进程一直在跑但什么事情都不做,子进程进入Z(僵尸态),此时就是task_struct仍然存在的原因,这样就会造成内存泄露的问题。

那么进程的终止不就是回收资源,防止内存泄露吗!

2.进程终止的三种情况?

a.代码跑完,结果正确

可以通过进程的退出码决定!!!

b.代码跑完,结果不正确

同样可以通过进程的退出码决定!!!

c.代码执行时,出现了异常,提前退出了

两个原因,一个系统检测到错误,异常退出,一个我们人为自定义!0退出码

异常退出?(本质是因为进程收到了OS发给进程的信号!)

vs编程运行的时候,崩溃了 --- 操作系统发现你的进程做了不该做的事情,OS杀了进程,一旦出现异常,退出码没有意义了!!!

我为什么出现了异常?原因是?进程出异常,本质是因为进程收到了OS发给进程的信号!

我们可以写一段野指针的操作代码来观察现象

我们发现运行时是出现了段错误,OS提前终止进程。

当我们启动一个进程也可以用kill -11 pid来终止一个进程,其实这就是我们手动发给进程一个信号,让进程异常终止,报的也就是上面的段错误,所以进程出异常,本质是因为进程收到了OS发给进程的信号!

我们可以看进程退出的时候,退出信号是多少,就可以判断我的进程为什么异常了!!!

总结

所以我们面对程序出现错误,两步判断就可

  1. 先确认是否是异常
  2. 不是异常就一定是代码跑完了,看退出码就行。

最后,我们说衡量一个进程退出,我们只需要两个数字退出码,退出信号

这些都是要让父进程bash知道。

其实退出码,退出信号就是在我们的task_struct源代码中的两个个变量

inr exit_code,int exit_signal

3.如何终止?

a.main函数return表示进程终止(非main函数return,函数结束)

b.代码调用exit函数(注意:我们代码的任意位置调用exit,都表示进程退出)

c._exit() --- system call 系统调用接口

exit与_exit都可以让进程退出,那么它俩有什么区别呢?

exit vs _exit:exit会在进程退出的时候,充刷缓冲区,_exit不会,其实这是因为exit是c语言的库函数,它的底层还会调用_exit这个系统调用接口,来操作操作系统,所以充刷缓冲区应该在exit函数用户层的操作。

注意:目前我们说的缓冲区不是内核缓冲区!!!

4.进程等待?

是什么?

结论:任何子进程,在退出的情况下,一般必须要被父进程进行等待。进程在退出的时候,如果父进程不管不顾,退出进程,状态Z(僵尸态),造成内存泄漏。

为什么?

1.父进程通过等待,解决子进程退出的僵尸问题,,回收系统资源(一定要考虑的)

2.获取子进程的退出信息,知道子进程是因为什么原因退出的(可选的功能)

怎么办?

父进程可以通过调用wait/waitpid函数来做到上面两个事情

pid_t wait(int* status)

如果子进程没有退出,父进程其实一直在进行阻塞等待

参数部分,等待父进程中,等任意一个子进程退出。

返回值,等待成功时,子进程的pid。

进程阻塞等待?

子进程本身就是软件,父进程本质是在等待某种软件条件就绪,你如何理解阻塞等待子进程呢?

status是什么呢?

这里就要讲讲waitpid了

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

返回值

当正常返回时waitpid返回收集到的子进程的进程id

如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0

如果调用中出错则返回-1,这时errno会被设置成相应的值以指示错误所在

参数

pid:

pid=-1,等待任一个子进程,与wait等效

pid>0,等待其进程ID与pid相等的子进程

status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。
 

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。
  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待。

所以status其实是个输出型参数,我们学过scanf函数,我们从键盘输入数据输出到一个变量中,status就类似把进程退出码与进程退出信号输出到status中,以便父进程拿到子进程的退出信息。

那status是如何存储进程退出码与进程退出信号两个数据呢?

其实status是使用了32位比特位来存储,用status的后7位来存储终止信号,第8位存储core dump(后面讲),再往后8位存储的是进程退出信息,再往后尚未使用。

 测试代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void ChildRun()
{
    int *p = NULL;
    int cnt = 5;
    while(1)
    {
        printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
        sleep(1);
        cnt--;
        *p = 100;
    }
}

int main()
{
    printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());

    pid_t id = fork();
    if(id == 0)
    {
        // child
        ChildRun();
        printf("child quit ...\n");
        exit(123);
    }
    sleep(7);
    // father
    //pid_t rid = wait(NULL);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        printf("wait success, rid: %d\n", rid);
    }
    else
    {
        printf("wait failed !\n");
    }
    sleep(3);
    printf("father quit, status: %d, child quit code : %d, child quit signal: %d\n", status, (status>>8)&0xFF, status & 0x7F);
}

运行结果

这个就是因为段错误,出现异常,进程异常退出!

进程的非阻塞式等待方式代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
	pid_t pid;
	pid = fork();
	if (pid < 0) {
		printf("%s fork error\n", __FUNCTION__);
		return 1;
	}
	else if (pid == 0) { //child
		printf("child is run, pid is : %d\n", getpid());
		sleep(5);
		exit(1);
	}
	else {
		int status = 0;
		pid_t ret = 0;
		do
		{
			ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
			if (ret == 0) {
				printf("child is running\n");
			}
			sleep(1);
		} while (ret == 0);
		if (WIFEXITED(status) && ret == pid) {
			printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));
		}
		else {
			printf("wait child failed, return.\n");
			return 1;
		}
	}
	return 0;
}

执行结果!