Linux 程序设计学习笔记----进程管理与程序开发(上)

转载请注明出处,http://blog.csdn.net/suool/article/details/38406211,谢谢!

Linux进程存储结构和进程结构

可执行文件结构

如下图:

可以看出,此ELF可执行文件存储时(没有调入内存)分为代码区、数据区和未出花数据区三部分。

代码区:存放cpu的执行的机器指令。

数据区:包含程序中的已经初始化的静态变量,以及已经初始化的全局变量。

未初始化数据区:存入的是未初始化的全局变量和未初始化的静态变量。

现在在上面的程序代码中增加一个int的静态变量,结果如下:

代码部分增加了4个字节,是int的大小。

进程结构

如果将一个ELF格式可执行文件加载到内存中运行,则将演变成一个或多个进程。进程是Linux事务管理的基本单元,所有的进程均拥有独立的环境和资源。

下图展示一个ELF可执行文件的存储结构和Linux进程基本结构的对照:

上图仅给出了一个进程的在内存中申请的代码区、初始化数据区、未初始化数据区、堆区、栈区五个部分。

为了更好的管理linux所访问的资源,系统定义了进程控制块(PCB)结构体来管理每个进程资源,如图:

而进程资源分为:

内核空间进程资源和用户空间进程资源

内核空间进程资源即PCB相关的信息。用户空间进程资源包括:通过成员mm_struct映射的内存空间。

进程状态

// 内核进程结构
#define TASK_RUNNING		0   // 就绪
#define TASK_INTERRUPTIBLE	1   // 中断等待
#define TASK_UNINTERRUPTIBLE	2   // 不可中断等待
#define __TASK_STOPPED		4   // 僵死
#define __TASK_TRACED		8   // 暂停
/* in tsk->exit_state */
#define EXIT_ZOMBIE		16
#define EXIT_DEAD		32
/* in tsk->state again */
#define TASK_DEAD		64
#define TASK_WAKEKILL		128
#define TASK_WAKING		256

系统中的每个进程都必然处于以上所列进程状态中的一种。

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依次增加,如下:

在应用编程中可以使用getpid()函数获取当前进程的PID.具体参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/getpid.html

父进程号(PPID)

除init进程外的所有进程都是由另一进程创建的,该进程成为父进程,被创建的进程成为子进程。PPID一样无法在用户层修改。使用getppid()函数获取当前进程的父进程号。参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/getppid.html

进程组号(PGID)

和用户管理一样,进程也有自己的进程号(PID)和进程组号(PGID)。进程组是一个或多个进程的集合。他们与同一个作业相关联,可以接受来自同一个终端的各种信号,但是进程组号可以在用户层修改。

使用getpgid()函数获取指定进程的进程组号。详见:http://pubs.opengroup.org/onlinepubs/009695399/functions/getpgid.html

下面一个程序是总和上面三个函数使用的示例:

#include<stdio.h>
#include<unistd.h>
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<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
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<stdio.h>
#include<unistd.h>
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 特殊进程

转载请注明出处,http://blog.csdn.net/suool/article/details/38406211,谢谢!

Linux 程序设计学习笔记----进程管理与程序开发(上)

时间: 2024-12-28 20:24:34

Linux 程序设计学习笔记----进程管理与程序开发(上)的相关文章

Linux 程序设计学习笔记----进程管理与程序开发(下)

转载请注明出处:http://blog.csdn.net/suool/article/details/38419983,谢谢! 进程管理及其控制 创建进程 fork()函数 函数说明具体参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/fork.html 返回值:Upon successful completion, fork() shall return 0 to the child process and shall re

Linux 程序设计学习笔记----POSIX 文件及目录管理

转载请注明:http://blog.csdn.net/suool/article/details/38141047 问题引入 文件流和文件描述符的区别 上节讲到ANSI C 库函数的实现在用户态,流的相应资源也在用户空间,但无论如何实现最终都需要通过内核实现对文件的读写控制.因此fopen函数必然调用了对OS的系统调用.这一调用在LINUX下即为open, close, read, write等函数.这些都遵循POSIX标准. so,在linux系统中是如何通过POSIX标准实现对文件的操作和目

Linux 程序设计学习笔记----Linux下文件类型和属性管理

转载请注明出处:http://blog.csdn.net/suool/article/details/38318225 部分内容整理自网络,在此感谢各位大神. Linux文件类型和权限 数据表示 文件属性存储结构体Inode的成员变量i_mode存储着该文件的文件类型和权限信息.该变量为short int类型. 这个16位变量的各个位功能划分为: 第0-8位为权限位,为别对应拥有者(user),同组其他用户(group)和其他用户(other)的读R写W和执行X权限. 第9-11位是权限修饰位,

Linux 程序设计学习笔记----ANSI C 文件I/O管理

转载请注明出处:http://blog.csdn.net/suool/article/details/38129201 问题引入 文件的种类 根据数据存储的方式不同,可以将文件分为文本文件和二进制文件.具体的区别和关系如下: 文本文件与二进制文件在计算机文件系统中的物理存储都是二进制的,也就是在物理存储方面没有区别都是01码,这个没有异议,他们的区别主要在逻辑存储上,也就是编码上. 文本文件格式存储时是将值作为字符然后存入其字符编码的二进制,文本文件用'字符'作为单位来表示和存储数据,比如对于1

Linux程序设计学习笔记----System V进程通信(共享内存)

转载请注明出处:http://blog.csdn.net/suool/article/details/38515863 共享内存可以被描述成内存一个区域(段)的映射,这个区域可以被更多的进程所共享.这是IPC机制中最快的一种形式,因为它不需要中间环节,而是把信息直接从一个内存段映射到调用进程的地址空间. 一个段可以直接由一个进程创建,随后,可以有任意多的进程对其读和写.但是,一旦内存被共享之后,对共享内存的访问同步需要由其他 IPC 机制,例如信号量来实现.象所有的System V IPC 对象

Linux程序设计学习笔记----System V进程通信之消息队列

一个或多个进程可向消息队列写入消息,而一个或多个进程可从消息队列中读取消息,这种进程间通讯机制通常使用在客户/服务器模型中,客户向服务器发送请求消息,服务器读取消息并执行相应请求.在许多微内核结构的操作系统中,内核和各组件之间的基本通讯方式就是消息队列.例如,在 MINIX 操作系统中,内核.I/O 任务.服务器进程和用户进程之间就是通过消息队列实现通讯的. Linux中的消息可以被描述成在内核地址空间的一个内部链表,每一个消息队列由一个IPC的标识号唯一的标识.Linux 为系统中所有的消息队

Linux程序设计学习笔记----System V进程间通信(信号量)

关于System V Unix System V,是Unix操作系统众多版本中的一支.它最初由AT&T开发,在1983年第一次发布,因此也被称为AT&T System V.一共发行了4个System V的主要版本:版本1.2.3和4.System V Release 4,或者称为SVR4,是最成功的版本,成为一些UNIX共同特性的源头,例如"SysV 初始化脚本"(/etc/init.d),用来控制系统启动和关闭,System V Interface Definitio

Linux程序设计学习笔记----进程间通信——管道

转载请注明出处: http://blog.csdn.net/suool/article/details/38444149, 谢谢! 进程通信概述 在Linux系统中,进程是一个独立的资源管理单元,但是独立而不孤立,他们需要之间的通信,因此便需要一个进程间数据传递.异步.同步的机制,这个机制显然需要由OS来完成管理和维护.如下: 1.同一主机进程间数据交互机制:无名管道(PIPE),有名管道(FIFO),消息队列(Message Queue)和共享内存(Share Memory).无名管道多用于亲

Linux 程序设计学习笔记----命令行参数处理

转载请注明出处.http://blog.csdn.net/suool/article/details/38089001 问题引入----命令行参数及解析 在使用linux时,与windows最大的不同应该就是经常使用命令行来解决大多数问题.比如下面这样的: 而显然我们知道C语言程序的入口是mian函数,即是从main函数开始执行,而main函数的原型是: int main( int argc, char *argv[] ); int main( int argc, char **argv );