UNIX环境高级编程 - - -进程1


前言

本文为笔者学习笔记,若有不妥之处,欢迎斧正。

一、进程相关概念

进程的定义:“进程”是操作系统的最基本、最重要的概念之一。但迄今为止对这一概念还没有一个确切的统一的描述。下面给出几种对进程的定义描述。进程是程序的一次执行。进程是可以并行执行的计算。进程是一个程序与其使用的数据在处理机上顺序执行时发生的活动。进程是程序在一个数据集合上的运行过程。它是系统进行资源分配和调度的一个独立单位。

进程的特征:动态性:是程序的一次执行;并发性:进程是可以并发执行;独立性:是系统进行资源分配和调度的一个独立单位;异步性:进程间的相互制约,使进程执行具有间隙;结构性:进程是具有结构的。

  • 什么是程序?什么是进程?两者有什么区别呢?

程序:是一组计算机能识别和执行的指令,运行于电子计算机上,满足人们某种需求的信息化工具。

进程:计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

区别:

  1. 程序会一直存储在磁盘中,进程是暂时的,是程序在内存中执行的过程,可以创建和撤销。
  2. 程序是静态的,而进程是动态的
  3. 进程具有并发性,而程序没有
  4. 进程是竞争计算机资源的最小单位,而程序不是
  5. 进程和程序不是一一对应的关系,一个程序可以有多个进程
  • 什么是进程标识符?

进程标识符:每个进程都有一个非负整型表示的唯一进程ID。

注意:虽然唯一,但是进程ID是可复用的。

可与使用getpid()获取自身的进程标识符,getppid()获取父进程的标识符。

  • 什么是父进程?什么是子进程?

当进程A创建了进程B,那么A就是父进程,B就是子进程。

  • C程序存储空间分配?

正文段:由CPU执行的机器指令部分。通常正文段是可共享的,所以即使是频繁执行的程序,在存储器中也只有一个副本,另外正文段常常是只读权限,以防止程序由于意外而修改其自身的指令。

初始化数据段:通常将称为数据段,包含了程序中明确赋予了初值的变量(使变量带有初始值,并存储在数据段中)。

非初始化数据段:通常称为bss段,名称来源是早期的汇编运算符(block started symbol),在程序开始执行之前,内核将此段中的数据初始化为0或者空指针。

堆:通常在队中进行动态存储分配 。

栈:自动变量以及每次函数调用时所需要的保存的信息都存放在此段中。

二、进程相关API详解

1、fork函数

函数原型:

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

函数作用:一个现有的进程可以调用fork函数创建一个新进程(子进程)。

参数说明:无参。

返回值:
成功:返回两次。一次是子进程返回0,父进程返回子进程的ID。
失败:返回-1。

说明:fork()函数创建进程相当于把父进程做了一个写时复制,并且给了子进程去执行。

进程的数据使用是指针,不是真实物理内存地址,
父进程fork一个子进程后,父进程和子进程的指针指向的是相同的内存地址,
这样子进程就获得了父进程的全部数据,
父进程和子进程是隔离的,父子进程遵循写时复制原则,对修改数据相互没有影响。

fork 有以下两种用法:

  1. 父进程希望复制自己,使父进程和子进程执行不同的代码段。应用场景:网络服务进程中——父进程等待客户端的服务请求。当请求到达时,调用fork使子进程处理请求,父进程继续等待下一个服务请求。
  2. 一个进程要执行一个不同的程序。这是shell是常见的情况,在这种情况下,子进程从fork返回后立即调用exec族函数。

注意事项:fork函数创建的父子进程,执行顺序是不确定的,两者相互争夺CPU资源

2、vfork函数

函数原型:

#include <sys/types.h>
#include <unistd.h>

pid_t vfork(void);

函数作用:一个现有的进程可以调用fork函数创建一个新进程(子进程)。

参数说明:无参。

返回值:
成功:返回两次。一次是子进程返回0,父进程返回子进程的ID。
失败:返回-1。

vfork函数和fork函数的区别:

  1. vork直接使用父进程的存储空间,不拷贝。
  2. vork函数保证子进程先运行,并且当子进程代用exit函数退出后,父进程才开始运行。
  3. vfork ()保证子进程先运行,在她调用exec 或exit 之后父进程才可能被调度运行。如果在 调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

3、进程退出函数

函数原型:

#include <unistd.h>
#include <stdlib.h>

void exit(int status);

void _exit(int status);

void _Exit(int status);

函数作用:进程退出。

参数说明:子进程状态(目的是为了通知父进程)

返回值:无。

进程正常退出:

  1. return
  2. exit(),属于标准C库
  3. _exit(),_Exit(),属于系统调用
  4. 进程的最后一个线程在其启动例程中执行return。
  5. 进程的最后一个线程调用pthread_exit 函数。

进程异常退出:

  1. 调用abort()
  2. Linux中强制终止进程(ctrl + c)
  3. 线程对取消请求作出响应

4、进程等待函数

4.1wait函数

函数原型:

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);

函数作用:父进程等待子进程退出,并收集子进程的退出状态。

参数说明:

  1. wstatus是一个整型指针,用来存放子进程退出时的状态,此时非空。若不关心退出状态,此时为空。

返回值分三种情况:

  • -1: 调用函数出错
  • 0: 若waitpid的options设置了WNOHANG,且调用中没有子进程退出,立即返回0
  • 大于0:返回退出进程的ID

4.2waitpid函数

函数原型:

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *wstatus, int options);

函数作用:父进程等待子进程退出,并收集子进程的退出状态。

参数说明:

  1. 等待进程的ID(监听进程的ID)
  2. wstatus是一个整型指针,用来存放子进程退出时的状态,此时非空。若不关心退出状态,此时为空。
  3. 提供一些额外的选项控制waitpid,目前linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数宏,可以使用 | 连接使用。

参数作用解释:

  • 参数pid>0时,只等待进程ID等于pid的子进程,只要指定的子进程没有结束,waitpid就会一直等待
  • 参数pid=0时,等待同一个进程组中的任一子进程,如果子进程已经加入别的进程组,waitpid不会对其进行处理
  • 参数pid=-1时,等待任何一个子进程退出,与wait作用一样
  • 参数pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的id等于pid的绝对值

返回值分三种情况:

  • -1: 调用函数出错
  • 0: 若waitpid的options设置了WNOHANG,且调用中没有子进程退出,立即返回0
  • 大于0:返回退出进程的ID

4.3wait和waitpid的联系

wait实质上是waitpid中pid=-1,options=0时封装,即wait(&status)与waitpid(-1, &status, 0)完全相同。

三、僵死进程和孤儿进程

1、僵死进程

定义:如果子进程退出的状态没有被父进程所收集,那么子进程就变成了僵死进程(僵尸进程)。

子进程退出返回的状态需要使用特殊的宏定义来解析。

详细例程和宏定义的使用,见后面的代码演示。

避免出现僵死进程:

  • 父进程调用 wait 或 waitpid 来收集资源
  • 用 kill 指令杀死父进程 语法:kill -9 pid
  • 通过信号机制,在处理函数中调用wait,回收资源

2、孤儿进程

定义:当调用fork和vfork后,就有父进程和子进程,如果两个进程都在运行,但是父进程比子进程先结束,导致只有子进程在运行时,此时的子进程就是孤儿进程。(简单来说就是父进程死了,子进程就成了孤儿进程)

注意事项:只有在父进程结束后,子进程才会变成孤儿进程

Linux系统中,为了避免出现过多的孤儿进程,init进程会收留孤儿进程,编程孤儿进程的父进程。(可以理解为继父)

被init进程收养后,会有操作系统来处理孤儿进程。

四、代码演示

  • 获取进程pid
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
        pid_t pid;

        pid = getpid();

        printf("pid=%d\n",pid);

        getchar();
        return 0;
}
  • 验证fork函数
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
        pid_t pid;

        pid = getpid();

        fork();   //调用后,下面的代码段都会被父子进程执行

        while(1)
        {
                if(pid == getpid())
                {
                        printf("this is father pid,pid=%d\n",getpid());
                }
                else
                {
                        printf("this is son pid,pid=%d\n",getpid());
                }
                sleep(2);
        }
        return 0;
}

运行结果:

this is father pid,pid=3745				//不仅验证了fork,也验证了父进程和子进程的执行顺序不确定
this is son pid,pid=3746				//两个进程会争夺CPU资源
this is son pid,pid=3746
this is father pid,pid=3745
this is father pid,pid=3745
this is son pid,pid=3746
this is son pid,pid=3746
this is father pid,pid=3745
this is son pid,pid=3746
  • 验证 fork函数 的返回值
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
        pid_t pid;

        pid = fork();

        while(1)
        {
                if(pid > 0)
                {
                        printf("this is father pid,pid=%d\n",getpid());
                }
                else
                {
                        printf("this is son pid,pid=%d\n",getpid());
                }
                sleep(2);

        }
        return 0;
}

运行结果:

this is father pid,pid=3775
this is son pid,pid=3776
this is father pid,pid=3775
this is son pid,pid=3776
this is father pid,pid=3775
this is son pid,pid=3776
  • 验证fork的写实复制
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>


int main(void)
{
        pid_t pid;
        int data = 10;

        pid = fork();
        printf("pid=%d\n",pid);
        if(pid > 0)
        {
                printf("this is father pid,pid=%d\n",getpid());
        }
        else if(pid == 0)
        {
                printf("this is son pid,pid=%d\n",getpid());
                data+=100;
        }
        printf("data:%d\n",data);

        return 0;
}

运行结果如下:

pid=3906
this is father pid,pid=3905
data:10
pid=0
this is son pid,pid=3906
data:110
  • fork 的应用模板
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>


int main(void)
{
        pid_t pid;

        pid = fork();

        if(pid > 0) 						//父进程执行的代码段
        {
                while(1)
                {
                        printf("this is father pid,pid=%d\n",getpid());
                        sleep(1);
                }
        }
        else if(pid == 0)				  //子进程执行的代码段
        {
                while(1)
                {
                        printf("this is son pid,pid=%d\n",getpid());
                        sleep(1);
                }
        }

        return 0;
}
  • 验证 fork 和 vfork 的区别
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
 
int main(void)
{
        pid_t pid;
        int *data=(int *)malloc(sizeof(int));


        *data = 0;

        pid = vfork();
        if(pid > 0)
        {
                while(1)
                {
                        printf("this is father pid,pid=%d\n",getpid());
                        printf("data:%d\n",*data);
                        sleep(1);
                }
        }
        else if(pid == 0)
        {
                while(1)
                {
                        printf("this is son pid,pid=%d\n",getpid());
                        (*data)++;
                        if(*data == 5)
                        {
                                exit(0);
                        }
                        sleep(1);
                }
        }

        return 0;
}

运行结果如下:

this is son pid,pid=3955
this is son pid,pid=3955
this is son pid,pid=3955
this is son pid,pid=3955
this is son pid,pid=3955
this is father pid,pid=3954
data:5
this is father pid,pid=3954
data:5
this is father pid,pid=3954
data:5
  • wait等待子线程退出
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(void)
{
        pid_t pid;
        int data=-1;

        pid = fork();

        if(pid > 0)
        {
                wait(NULL);
                while(1)
                {
                        printf("this is father pid,pid=%d\n",getpid());
                        printf("data:%d\n",data);
                        sleep(1);
                }
        }
        else if(pid == 0)
        {
                while(1)
                {
                        printf("this is son pid,pid=%d\n",getpid());
                        data++;
                        if(data == 5)
                        {
                                exit(3);
                        }
                        sleep(1);
                }
        }

        return 0;
}

运行结果如下:

this is son pid,pid=4269
this is son pid,pid=4269
this is son pid,pid=4269
this is son pid,pid=4269
this is son pid,pid=4269
this is son pid,pid=4269
this is father pid,pid=4268
data:-1
this is father pid,pid=4268
data:-1
  • wait等待线程退出,并收集子线程的退出状态
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>


int main(void)
{
        pid_t pid;
        int data=-1;

        pid = fork();

        if(pid > 0)
        {
                wait(&data);
                while(1)
                {
                        printf("this is father pid,pid=%d\n",getpid());
                        printf("data:%d\n",WEXITSTATUS(data));
                        sleep(1);
                }
        }
        else if(pid == 0)
        {
                while(1)
                {
                        printf("this is son pid,pid=%d\n",getpid());
                        data++;
                        if(data == 5)
                        {
                                exit(3);
                        }
                        sleep(1);
                }
        }

        return 0;
}

运行结果如下:

this is son pid,pid=4303
this is son pid,pid=4303
this is son pid,pid=4303
this is son pid,pid=4303
this is son pid,pid=4303
this is son pid,pid=4303
this is father pid,pid=4302
data:3
this is father pid,pid=4302
data:3
this is father pid,pid=4302
data:3
  • waitpid的使用
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>


int main(void)
{
        pid_t pid;
        int data=-1;

        pid = fork();

        if(pid > 0)
        {
                waitpid(pid,&data,WNOHANG);
                while(1)
                {
                        printf("this is father pid,pid=%d\n",getpid());
                        printf("data:%d\n",WEXITSTATUS(data));
                        sleep(1);
                }
        }
        else if(pid == 0)
        {
                while(1)
                {
                        printf("this is son pid,pid=%d\n",getpid());
                        data++;
                        if(data == 5)
                        {
                                exit(3);
                        }
                        sleep(1);
                }
        }

        return 0;
}

运行结果如下:

this is father pid,pid=4319
data:255
this is son pid,pid=4320
this is father pid,pid=4319
this is son pid,pid=4320
data:255
this is son pid,pid=4320
this is father pid,pid=4319
data:255
this is father pid,pid=4319
this is son pid,pid=4320
data:255
this is son pid,pid=4320
this is father pid,pid=4319
data:255
this is son pid,pid=4320
this is father pid,pid=4319
data:255
this is father pid,pid=4319
data:255

后期会补充一些相关的案例和文中没有涉及到的知识。


参考资料:
《UNIX环境高级编程》 第三版

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值