Linux进程存储结构和进程结构
可执行文件结构
如下图:
可以看出,此ELF可执行文件存储时(没有调入内存)分为代码区、数据区和未出花数据区三部分。
代码区:存放cpu的执行的机器指令。
数据区:包含程序中的已经初始化的静态变量,以及已经初始化的全局变量。
未初始化数据区:存入的是未初始化的全局变量和未初始化的静态变量。
现在在上面的程序代码中增加一个int的静态变量,结果如下:
代码部分增加了4个字节,是int的大小。
进程结构
如果将一个ELF格式可执行文件加载到内存中运行,则将演变成一个或多个进程。进程是Linux事务管理的基本单元,所有的进程均拥有独立的环境和资源。
下图展示一个ELF可执行文件的存储结构和Linux进程基本结构的对照:
上图仅给出了一个进程的在内存中申请的代码区、初始化数据区、未初始化数据区、堆区、栈区五个部分。
为了更好的管理linux所访问的资源,系统定义了进程控制块(PCB)结构体来管理每个进程资源,如图:
而进程资源分为:
内核空间进程资源和用户空间进程资源
内核空间进程资源即PCB相关的信息。用户空间进程资源包括:通过成员mm_struct映射的内存空间。
进程状态
// 内核进程结构
#define TASK_RUNNING0 // 就绪
#define TASK_INTERRUPTIBLE1 // 中断等待
#define TASK_UNINTERRUPTIBLE2 // 不可中断等待
#define __TASK_STOPPED4 // 僵死
#define __TASK_TRACED8 // 暂停
/* in tsk->exit_state */
#define EXIT_ZOMBIE16
#define EXIT_DEAD32
/* in tsk->state again */
#define TASK_DEAD64
#define TASK_WAKEKILL128
#define TASK_WAKING256
系统中的每个进程都必然处于以上所列进程状态中的一种。
TASK_RUNNING表示进程要么正在执行,要么正要准备执行。
TASK_INTERRUPTIBLE表示进程被阻塞(睡眠),直到某个条件变为真。条件一旦达成,进程的状态就被设置为TASK_RUNNING。
TASK_UNINTERRUPTIBLE的意义与TASK_INTERRUPTIBLE类似,除了不能通过接受一个信号来唤醒以外。
__TASK_STOPPED表示进程被停止执行。
__TASK_TRACED表示进程被debugger等进程监视。
EXIT_ZOMBIE表示进程的执行被终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信息。
EXIT_DEAD表示进程的最终状态。
EXIT_ZOMBIE和EXIT_DEAD也可以存放在exit_state成员中。进程状态的切换过程和原因大致如下图(图片来自《Linux Kernel Development》)
进程基本属性
进程号(PID)
PID是系统维护的唯一标示一个进程的正整数,进程号是无法在用户层修改的。在Linux系统中,系统的第一个用户进程是init进程,PID是1,其他的进程的PID依次增加,如下:
父进程号(PPID)
除init进程外的所有进程都是由另一进程创建的,该进程成为父进程,被创建的进程成为子进程。PPID一样无法在用户层修改。使用getppid()函数获取当前进程的父进程号。参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/getppid.html
进程组号(PGID)
和用户管理一样,进程也有自己的进程号(PID)和进程组号(PGID)。进程组是一个或多个进程的集合。他们与同一个作业相关联,可以接受来自同一个终端的各种信号,但是进程组号可以在用户层修改。
下面一个程序是总和上面三个函数使用的示例:
#include
#include
int main(int argc,char *argv[])
{
int i;
printf("\tpid\t ppid \t pgid\n"); // 提示信息
printf("parent\t%d\t%d\t%d\n",getpid(),getppid(),getpgid(0)); // 当前进程信息
for(i=0;i<2;i++)
if(fork()==0)
printf("child\t%d\t%d\t%d\n",getpid(),getppid(),getpgid(0)); // 子进程信息
return 0;
}
此外,getpgrp()也可以用来获取当前进程的进程组号。参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/getpgrp.html
会话
会话(session)是一个或多个进程的合集。系统调用函数getsid()用来获取某个进程会话的SID。
参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/getsid.html
某进程的sid是可以修改的,函数setsid()用于创建新的会话。here : http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html
控制终端
会话和进程组有如下特点:
为了让终端设备驱动程序将信号送到进程,可以调用tcgetpgrp()获取前台进程组的进程组号。返回与打开的终端相关联的前台进程组号。http://pubs.opengroup.org/onlinepubs/009695399/functions/tcgetpgrp.html
tcsetpgrp()函数用来设置某个进程组是前台还是后台进程组。http://pubs.opengroup.org/onlinepubs/009695399/functions/tcsetpgrp.html
如果进程有一个控制终端,则将前台进程组ID设置为pgrpid,这个值应该在用一个会话中的一个进程组的id,参数fileds是控制终端文件描述符。
tcgetsid(*)函数获取当前控制终端的会话首进程的会话id。http://pubs.opengroup.org/onlinepubs/009695399/functions/tcgetsid.html
下面是使用上面的函数示例:
#include
#include
#include
#include
int main()
{
int fd;
pid_t pid;
pid=fork(); // 创建新进程
if(pid==-1)
perror("fork");
else if(pid>0)
{
wait(NULL);
exit(EXIT_FAILURE);
}
else
{
if((fd=open("/dev/pts/0",O_RDWR))==-1) // 因为是网络终端,在此打开终端以确认
{
perror("open");
}
printf("pid=%d,ppid=%d\n",getpid(),getppid()); // 获取进程号& 父进程
printf("sid=%d,tcgetsid=%d\n",getsid(getpid()),tcgetsid(fd)); // 读取会话sid和终端sid
printf("tcgetpgrp=%d\n",tcgetpgrp(fd)); // 读取终端前台进程
printf("pigd=%d\n",getpgid(getpid())); // 读取进程组的ID
}
}
运行结果:
进程用户属性
Linux是权限哟有严格控制的操作系统,某个进程拥有真实的用户号(RUID)、真实用户组号(RGID)、有效用户号(EUID)、有效用户组号(EGID)信息。
在Linux系统中,文件的创建者是文件的拥有者,即文件的真实用户号为文件的拥有者号,使用ls -l 命令查看。
进程真实用户号(RUID)
对于进程而言,创建该进程的用户uid即是此进程的真实用户号。使用getuid(0函数获取当前进程的RUID。函数定义在unistd.h中。
具体参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/getuid.html
进程有效用户组号(EUID)
主要用于权限检查。多数情况下,EUID和UID相同,但是如果可执行文件的setuid位有效,则该文件的拥有者之外的用户在运行程序时,EUID和UID不一样,即当某个可执行文件设置了 setgid位后,任何用户(包括root)运行此程序时,其有效用户组EUID为该文件的拥有者。sunch as:
注意setuid位的特殊性,这个以后会专门讲解。
进程用户组号(GID)
创建进程的用户所在的组号为该进程的用户组号(GID),可以调用getgid()函数来获取当前进程的真实用户组号。
具体参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/getgid.html
下面是读取当前进程的UID/GID/EUID/EGID的示例程序:
#include
#include
int main(int argc,char *argv[])
{
printf("\tuid\tgid\teuid\tegid\n");
printf("parent\t%d\t%d\t%d\t%d\n",getuid(),getgid(),geteuid(),getegid());
if(fork()==0)
{
printf("child\t%d\t%d\t%d\t%d\n",getuid(),getgid(),geteuid(),getegid());
}
return 0;
}
Next
进程管理及控制
Linux 特殊进程