Linux中进程的内存结构、线程以及守护进程

一、进程的内存结构

在Linux系统中,采用虚拟内存管理技术,这样就是的每个进程都有自己独立的地址空间。这片地址空间的大小为4G的线性虚拟内存空间,我们在使用是都是该虚拟内存的虚拟地址,无法直接访问到物理内存地址,这种使用虚拟地址比直接访问物理地址更加安全,切内存空间的利用率也更充分,能够使用更大的地址空间。
4G的进程地址空间被分为两个部分–用户空间和内核空间,用户空间划分在0-3G,内核空间划分为3-4G。用户进程在通常情况下只能访问用户空间的虚拟地址,不能访问内核地址,只有用户进程使用 系统调用时才可以访问到内核空间。后续的进程间通信中的管道通信中两个进程实现通信的原理就是通过访问内核地址实现,但内核地址的访问也有不同,这里不做过多描述。
下面具体介绍用户空间,用户空间包括以下几个功能区(通常称为段)
(1)、只读段:具有只读属性,包含程序代码和只读数据
(2)、数据段:存放着全局变量的静态变量。其中初始化数据段存放现实初始化的全局变量的静态变量,未初始化数据段,此段通常被称为BSS段,存放未初始化的全局变量和静态变量。
(3)、栈:由系统制动分配释放,存放函数的参数值、局部变量的值、返回地址等。
(4)、堆:存放动态分配的数据,一般由程序员动态分配和释放,若程序员不释放,在程序结束后一般会由操作系统回收。
(5)、共享库的内存映射区:这是Linux动态链接器和其他共享库代码的映射区域。

在这里插入图片描述

二、线程

前面提到过,进程是系统中程序执行和资源分配的基本单位。每个进程都拥有自己的数据段、代码段和堆栈,这就造成了进程在进行切换时操作系统的开销比较大。为提高效率,操作系统又引入了另个一新的概念–线程,线程也称为轻量级进程。线程可以对进程的内存空间和资源进程访问,并与同一进程中的其他线程共享。
一个进程可以拥有多个线程,其中每个线程都可以共享该进程中所拥有的资源。由于线程共享同一个进程的地址,故在对任何一个线程更改是都可能会对其他线程造成影响,由此可见多线程中线程的同步是很重要的问题。
在这里插入图片描述

三、守护进程

1 、守护进程的概述

守护进程也就是常说的Daemon进程,是Linux中的后台服务进程。他是一个生存周期较长的进程,通常独立于控制终端并且周期性的执行某种任务或者等待处理某些发生的事件。守护进程通常在系统启动时开始执行,在系统关闭时结束。在Linux中许多系统服务都是通过守护进程实现的。
在Linux中每一个从终端开始运行的进程都会依附于该终端,这个终端称为这些进程的控制终端。当终端关闭时,想要的进程都会自动结束。但守护进程却能够突破这一种限制,不受终端的影响。所以,当我们需要某一进程不因用户、终端或者其他的变化受到影响,我们就可以把这个进程变为守护进程。

2、编写守护进程

守护进程的编程分为以下几个步骤,每个步骤都有对应的函数,这里会对其相关的函数进行介绍。

(1)创建子进程,父进程退出。

这是编写守护进程的第一步。由于守护进程是脱离终端控制的,因此,完成第一步后子进程会变成后台进程,给用户感觉程序已经运行完毕。后面的其他操作都在子进程中完成,用户可以通过shell可以执行其他命令,从而在形式上做到了脱离终端。这里有一个有趣的现象,在父进程先于子进程退出后,子进程会变为一个孤儿进程,此时该子进程就会由1号进程(init进程)收养,这样原先的子进程就会变成1号进程的子进程。

pid_t pid = fork();
if(pid > 0)
{
	exit(0);  //父进程退出。
}
(2)在子进程中创建新会话。

这个步骤是创建守护进程中最重要的一步,虽然实现起来很简单,但作用意义非常重大。这里使用的函数是setsid()。这里函数介绍之前先介绍两个概念:进程组和会话组。

①进程组:进程组是一个或者多个进程的集合。进程组由进程组ID来唯一标识。除了进程号(PID)外,进程组ID也是一个进程的必备属性。
每个进程组都有一个进程组组长,称为组长进程,组长进程的ID等于进程组ID,且进程组ID不会随组长进程的退出而受影响。

②会话组:会话组是一个或者多个进程组的集合。通常一个会话开始于用户登录,终止与用户退出;也可以说是开始于终端打开,结束于终端关闭。会话组的对一个进程称为会话组组长。在此期间用户运行的所有进程都属于该会话组。

下面介绍setsid()函数:
setsid()函数用于创建一个新的会话,并且担任该会话组的组长,该函数有以下三个功能:
①让进程脱离原会话的控制。
②让进程脱离原进程组的控制。
③让进程脱离原控制终端的控制。
这里说明以下为什么要使用setsid()函数,在我们创建子进程的时候,子进程复制了父进程的会话组、进程组和控制终端等,这里虽然父进程退出了,但原先的会话组、进程组和控制终端并没有改变,因此并不能称为真正意义上的独立,使用setsid()函数就能够让子进程实现真正的独立。

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

pid_t setsid(void);

返回值:
	成功返回:该进程组ID
	失败返回:-1 
(3)改变当前目录。

这一步骤需要看你自己的使用情况而定,创建子进程时子进程继承的父进程的当前工作目录。在进程运行过程中当前的工作目录是不能卸载的,这对以后的使用可能会产生许多麻烦。我们通常的做法是将目录更改到根目录下,这样就可以避免一些问题。这里使用函数为chdir()。

(4)设置文件权限掩码。

文件权限掩码的作用是屏蔽文件权限中对应位。由于子进程继承的父进程的文件权限掩码,当子进程需脱离父进程时在使用这些文件可能会因为权限问题倒置无法使用。因此,把文件权限掩码设置为0,可以增强该守护进程的灵活性。这里设置文件权限掩码的函数为umask(),通常使用umask(0).

(5)关闭文件描述符

这里同we年权限一样,子进程在父进程继承的一些以及打开了的文件。这些打开的文件可能会永远不会被守护进程访问,但这些文件的打开占据的一部分系统资源,而且还可能导致所在文件系统无法被卸载。由于守护进程与终端无关,所以指向终端设备的标准输入、标准输出和标准错误流已经失去了意义(这三个流分别对应的文件描述符为0,1,2)应当被关闭。通常使用如下方法进行关闭

int num;
num = getdtablesize(); //获取当前进程文件描述符表大小
forint i=0;i<numli++{
	close(i);
}

通过以上5个步骤,一个简单的守护进程就创建好了。下面我们使用以上步骤创建一个守护进程,让该进程没隔1s将当前时间记录:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>

int main()
{
	pid_t pid = fork(); //创建子进程
	if(pid < 0)
	{
		perror("fork");
		exit(-1);
	}
	if(pid > 0) //关闭父进程
	{
		exit(-1);
	}
	else
	{
		setsid();  //在子进程中创建新的会话
//		chdir("/tmp"); //改变当前目录,这里可以选择更改也可以选择在当前目录,这里为了方便展示运行结果,所以我选在当前目录下
		umask(0);  //设置文件权限掩码
		int n;
		for(n=0;n<getdtablesize();n++) // 关闭文件描述符
		{
			close(n);
		}
		int i=1;
		FILE *fp = fopen("time.log","a+"); //创建并打开time.log文件
		if(fp < 0)
		{
			perror("open");
			exit(-1);
		}
		time_t tm;
		while(i){                    //向文件中每隔1s写入当前时间
			tm = time(0);
			fprintf(fp,"%s \n",asctime(localtime(&tm)));
			fflush(fp);
			sleep(1);
		}	
	}
}

运行结果如下所示
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们在运行程序后可以看到在当前目录下创建了一个time.log的文件,我们通过ps -ef指令查看进程发现a.out程序正在运行,此时我们将其终端关闭后,进入新建的文件后可以看出一人还是在没隔1s在记录当前时间,此时也能体现出守护进程与终端无关。最后我们通过kill -9 2954(进程号)才将该进程结束。
在这里插入图片描述

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值