linux系统编程:进程初步

进程的概念

        进程是一个运行着的程序,它包含了程序在运行时的各个资源,进程是操作系统进行调度的基本单位,也是 一个程序运行的基本单位。进程是一个程序一次执行的过程,是操作系统动态执行的基本单元。

进程的概念主要 有两点:

        第一,进程是一个实体。每个进程都有自己的虚拟地址空间,这些地址空间包括代码区、数据区、和堆 栈区。文本区域存储处理器执行的代码;数据区存储变量和动态分配的内存;堆栈区存储活动进程动态申请的内 存和局部变量及函数调用时的返回值。

int main()
{
    int a = 20;
    int *p = &a;
    pid_t pid = fork();//fork()函数的作用为创建一个子进程,
                       //返回值pid在子进程中为0,父进程中为子进程的pid

    if (pid == 0)
    {
        printf("子进程:\n");
        printf("%d\n", ++(*p));
        printf("%p\n", p);
    }
    else
    {
        wait(NULL);//wait()函数的作用是等待子进程死亡
        printf("父进程:\n");
        printf("%d\n", *p);
        printf("%p\n", p);
    }
}

执行结果如下: 

从结果可以看出子进程和父进程中指针p指向的地址相同,但是一个输出为21一个却是20,这是因为我们代码中分配的地址是虚拟地址并不是真实的物理地址,两者虚拟地址相同,但物理地址却不同。

        第二,进程是一个“执行中的程序”,它和程序有本质区别。程序是静态 的,它是一些保存在磁盘上的指令的有序集合(文件);而进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建、调度和消亡的过程,是 Linux 的基本调度单位。只有当处理器赋予程序生命时,它才能成 为一个活动的实体,称之为进程, 只是程序在内存,并能够得到执行的时候。

这是一个可执行程序

当执行之后,就会产生一个进程。

进程的状态

        进程是程序的执行过程,根据它的生命周期可以划分成 3 种状态。
执行态:该进程正在运行,即进程正在占用 CPU , 任何时候都只有一个进程。
就绪态:进程已经具备执行的一切条件,正在等待分配 CPU 的处理时间片。
等待态:进程正在等待某些事件,当前不能分配时间片,进程不能使用 CPU ,若等待事件发生(等待的资 源分配到)则可将其唤醒,变成就绪态。

进程的描述

        操作系统会为每个进程分配一个唯一的整型 ID ,做为进程的标识号 (pid) 。进程标识是无法在用户层修改的 (用户程序不能自己来修改自己的 pid, 这是操作系统分配的)。
        进程除了自身的 ID 外, 还有父进程 ID(ppid), 所有进程的祖先进程是同一个进程, 它叫做 init 进程, ID 1 init 进程是内核启动后的运行的第一个进程。
        init 进程负责引导系统、 启动守护(后台)进程并且运行必要的程序。它不是系统进程,但它以系统的超级用户特权运行使用命令 ps – aux 查看当前系统所有进程的基本属性。
ps 命令:类似任务管理器, ps 为我们提供了进程的一次性的查看,它所提供的查看结果并不动态连续的;如果想对进程实时监控,应该用 top 工具;常用形式: ps aux ps ef 执行 ps aux 之后:
USER 进程的属主;
PID 进程的 ID ;(是唯一的数值,用来区分进程)
PPID 父进程;
%CPU 进程占用的 CPU 百分比;
%MEM 占用内存的百分比;
NI 进程的 NICE 值,数值大,表示较少占用 CPU 时间;
VSZ 进程虚拟大小;
RSS 驻留中页的数量;
TTY 终端 ID;
WCHAN 正在等待的进程资源;
stat 进程状态;(运行 R 、休眠 S 、僵尸 Z 、停止或被追踪 T 、死掉的进程 X 、优先级较低的进程 N 、优先级高 的进程< 、进入内存交换 W 、非中断休眠(常规 IO D
START 启动进程的时间;
TIME 进程消耗 CPU 的时间;
COMMAND 命令的名称和参数;

进程资源

        对于我们编写的一个可执行文件来说, 它并没有包含上面所说的每个段。 可执行文件中只包含了代码段, 只读数据段,可读可写数据段的内容。 文件中不包含 bss 段的内容。可以使用 size 命令 查看程序的各个段的 大小。
示例:
Linux 中的进程包含以下几个部分,各部分说明如下:
1 )代码区。加载的是可执行文件代码段, 可执行代码,在有操作系统支持时,程序员不需要关注这一位置。 代码区通常是只读的,只读的原因是防止程序意外地修改了它的指令。
2 )数据段。该区包含在程序中明确被初始化的全局变量,已经初始化的静态变量和常量数据。存储于该区的 数据的生存周期为整个程序运行过程。
3 )未初始化数据区( BSS 段), 存入的是全局未初始化变量和未初始化静态变量还有初始值为 0 的变量。 BSS 段的数据在程序开始之前的值都为 0 ,在程序退出时才释放。
4 )栈区。由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间的过程。
5 )堆区。用于动态内存分配。 堆区一般由程序员分配和释放,我们使用 malloc 申请的内存都属于堆区内存。

创建进程

fork()函数

头文件:

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

        pid_t fork(void);

函数描述:

        fork()函数通过复制调用他的进程来创造进程。新的进程被叫做子进程,调用fork()的进程被称为父进程。fork()函数创建的进程会开辟一个新的空间,复制父进程所有的资源,并执行fork()函数之后的代码。

返回值:

        成功时:在子进程中返回值PID为0,在父进程中返回值为子进程的PID。

        失败时:返回值为-1,创建子进程失败。

代码演示:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    fork();
    fork();
    fork();
    printf("hello world\n");
}

执行结果:

一共打印了8次hello world,其原理如下。

vfork 函数

头文件:

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

pid_t vfork(void);

函数描述:

vfork 函数其作用和返回值与 fork 相同,但有一些区别。二者都创建一个子进程,但是它并不是将父进程的地址空间完全复制到子进程中,此时子进程是共享父进程的代码段以及数据段。vfork 保证子进程比父进程先运行,在它调用 exec 或 exit  (exec和exit后面会讲,现在只需要知道他们可以终结子进程即可) 之后父进程才可能被调度运行。

返回值:

        成功时:在子进程中返回值PID为0,在父进程中返回值为子进程的PID。

        失败时:返回值为-1,创建子进程失败。

代码演示:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <dirent.h>
#include <sys/wait.h>
int main()
{
	int a=0;
	int fatherSign=0;
	int sonSign=0;
	while (1)
	{
	pid_t pid=vfork();
	if(pid==0)
	{
		printf("son's home\n");
		printf("sign:%d\n",fatherSign);
		exit(11);
	}
	if(pid!=0)
	{
		printf("daddy's home\n");
		printf("sign:%d\n",fatherSign++);
	}
		a++;
		if(a==2)
		break;
	}
	return (0);
}

运行结果:

从上述执行结果,可以看到每次son都在daddy之前执行,且变量是共用的。

关闭进程

exit()函数

头文件:

#include <stdlib.h>

void exit(int status);

函数描述:

exit()的功能是用来正常关闭子进程的,并将status的值返回个父进程。(配合wait函数使用)
函数的参数:
status:退出进程的状态 0~255 ,status&0xFF 的值返回给了父进程 。

返回值:

 无返回值;

_exit()函数

头文件:

#include <unistd.h>

void _exit(int status);

函数描述:

_exit()的功能和exit()函数功能基本一致,但有不同点。exit 函数和_exit 函数的最大区别在于 exit 函数在退出之前会清空缓冲区,而_exit()退出不会清空缓存区。如果你打开了一个文件,又用了_exit(),而此时缓冲区又没满,就有可能出现数据丢失的情况。
函数的参数:
status:退出进程的状态 0~255 ,status&0xFF 的值返回给了父进程 。

返回值:

无返回值;


int main()
{

	pid_t pid=fork();
	if(pid==0)
	{
		printf("son's home");
		exit(11);
	}
	if(pid!=0)
	{
		int value=0;
		wait(&value);//等待子进程关闭在执行父进程,为了美观,不必理会
        printf("\n");
		printf("daddy's home");
		_exit(11);

	}
	return (0);
}

运行结果:

可以看到执行结果是子进程打印出结果,但是父进程有一句却没有打印出来。这是因为父进程调用了_exit()函数,进程结束了,却没有清空缓存区。

如果有同学不知道缓冲区,我只简单的说一下清空缓冲区的条件:出现"\n"、缓冲区满了、使用了fflus()函数等等。要想深入学习缓冲区,建议去看看其他写缓冲区的帖子。

进程资源回收

如果子进程先退出,系统不会自动清理掉子进程的环境和资源,而必须由父进程调用 wait waitpid 函数 来完成清理工作,如果父进程不做清理工作,则已经退出的子进程将成为僵尸进程, 在系统中如果存在 的僵尸 进程过多,将会影响系统的性能,所以必须对僵尸进程进行处理。
wait()函数

头文件:

函数头文件
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);

函数描述:

阻塞当前进程等待子进程死亡 ,子进程死亡后会进行资源的回收利用并获取子进程的退出状态。可以接收到子进程exit或_exit函数中的参数;

函数的参数:
wstatus:用来保存子进程的退出状态
通过宏定义可以解出来子进程是否为正常退出以及子进程中
调用 exit 或者_exit 函数的参数
WIFEXITED(wstatus):获取子进程是否为正常退出
exit _exit main 函数中的 return 都会返回真
WEXITSTATUS(wstatus):获取 exit _exit 中的参数值
函数的返回值:
成功返回 死亡的子进程进程号
失败返回 -1
代码演示:
int main()
{
		pid_t pid = fork();
		if (pid == 0)
		{
			printf("子进程关闭\n");
			exit(11);
		}
		else
		{
			int wstatus;
			wait(&wstatus);
			printf("如果退出成功WIFEXITED输出1\n");
			printf("WIFEXITED输出结果:%d\n",WIFEXITED(wstatus));
			printf("WEXITSTATUS解出来为exit退出时的参数\n");
			printf("WEXITSTATUS输出结果:%d\n",WEXITSTATUS(wstatus));
		}
}

执行结果:

waitpid()函数

头文件:

函数头文件
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);

函数描述:

阻塞当前进程等待子进程死亡 ,子进程死亡后会进行资源的回收利用并获取子进程的退出状态。可以接收到子进程exit或_exit函数中的参数;

函数的参数:
函数的参数:
pid: -1 任意的子进程 跟 wait 类似;0 同组下的任意子进程; >0 等待进程号为 pid 的进程退出
wstatus:保存子进程的退出状态
options:0 阻塞
WNOHANG 非阻塞
函数的返回值:
失败返回-1
如果 options 中使用了 WNOHANG
这个函数执行的时候没有任何的子进程死亡 返回 0
成功返回 死亡的子进程号
代码演示:
int main()
{
		pid_t pid = fork();
		if (pid == 0)
		{
			printf("子进程关闭\n");
			exit(11);
		}
		else
		{
			waitpid(pid,NULL,WNOHANG);
			printf("父进程死亡\n");
			exit(22);

		}
}

执行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值