存档在 2011年9月

动态的进程

2011年9月29日

进程是一个动态的实体,它是程序的一次执行过程,是操作系统中资源分配的基本单位。程序和进程的最大区别在于进程是动态运行着的,它存在于内存中;程序是静态的,它存在于磁盘上。

这里的程序指的是可执行文件(并不包含脚本文件),通常我们所说的源程序要通过预编译、编译、汇编和链接四个步骤转化为可执行程序。当我们运行可执行程序时,操作系统将可执行程序读入内存,程序摇身一变转化为一个进程。

1.进程的状态

进程是一个活体,因此也就随之诞生了进程的状态。在Linux内核中,几种经典的进程状态如下:

可运行状态:表示进程正在运行或者正在等待被运行,也就是操作系统原理中的运行态和就绪态。Linux使用TASK_RUNNING宏表示此状态。

可中断的等待状态:进程正在等待某个事件完成,即操作系统原理中等待态(或阻塞态、睡眠态)。处于该状态的进程属于“轻度睡眠”,它可以被信号或者定时器唤醒。Linux中使用TASK_INTERRUPTIBLE来表示此状态。

不可中断的等待状态:也是一种等待状态,只不过处于该状态的进程处于“深度睡眠”,它不能被信号或者定时器唤醒,只有当其等待的事件发生时才可以被唤醒。Linux中使用TASK_UNINTERRUPTIBLE来表示该状态。

僵死状态:进程已经终止,但是它的进程描述符仍然没有被操作系统收回,此时的进程只有身躯并无灵魂,需要父进程通过wait族函数为其收尸。Linux中使用EXIT_ZOMBIE表示该状态。

停止状态:进程因为收到SIGSTOP等信号停止运行。Linux中使用__TASK_TRACED表示该状态。

除了上述几种经典状态外,可以在sched.h中查看其他几种进程的状态。我们通过ps命令在shell中查看当前系统所有进程的状态信息。比如:

edsionte@edsionte-desktop:~/linux-3.0.4$ ps -eo pid,stat,command
  PID STAT COMMAND
    1 Ss   /sbin/init
    2 S    [kthreadd]
    3 S    [migration/0]
    4 S    [ksoftirqd/0]
    5 S    [watchdog/0]
    6 S    [migration/1]
    7 S    [ksoftirqd/1]
    8 S    [watchdog/1]

每行依次显示的是进程的pid、进程当前的状态和进程名。ps命令所显示的进程状态通过一组字母符号来表示,各种符号的含义如下:

       D    Uninterruptible sleep (usually IO)
       R    Running or runnable (on run queue)
       S    Interruptible sleep (waiting for an event to complete)
       T    Stopped, either by a job control signal or because it is being traced.
       W    paging (not valid since the 2.6.xx kernel)
       X    dead (should never be seen)
       Z    Defunct ("zombie") process, terminated but not reaped by its parent.

2.进程的内存映像

在Linux中可执行文件(此处先忽略脚本文件)的格式为ELF,即Executable and Linking Format。虽然可执行文件是二进制文件,但是它的内部还是划分着一些区域,这些区域称为可执行文件的段。这里的段和X86体系架构中的段是两个完全不同的概念。

通常可执行文件包含一个文本段(代码段),数据段。有时候可执行文件中还额外包含一个BSS段,这个段中存放着那些在源程序中没有被初始化的全局变量。

当运行可执行文件时,内核将可执行文件读入内存,我们将读入内存中的可执行文件称之为进程的内存映像。进程的内存映像虽然也根据存放内容的不同将进程的虚拟地址空间划分为一块一块的,但是这些区域在内存中并不称之为段,而是虚拟内存区域,内核使用vm_area_struct结构来表示这片内存区域。

可执行文件和进程的内存映像虽然都有代码段和数据段,但两者其实是不同的。首先可执行文件(也就是程序)是静态的,存放在磁盘上,而进程的内存映像只有在程序运行时才产生;其次,可执行文件没有堆栈段,而进程的内存映像是包含堆栈段的,因为堆(heap)用于动态分配内存,而栈(stack)需要保存局部变量、临时数据和传递到函数的参数等。从这些不同点也可以说明程序是动态的。

可执行文件的段在进程地址空间中的分布图可以参考如下:

需要注意的是,进程的地址空间并不只包含一个文本段或数据段,由于大多数源程序的目标文件在链接时都需要链接一些动态库,最终形成可执行文件,因此进程的地址空间中会有好几个文本段和数据段区域。而且在链接过程中,还需要加入链接器(ld),因此进程地空间精确的示意图如下:

 

在一个进程的内存映像中,文本段用来存放二进制的可执行代码,而数据段用来存放全局变量和静态变量,BSS段用来存放未初始化的全局变量,堆段用于为malloc函数分配空间,栈段用来存放局部变量和形参等临时数据。

参考:

1. Linux C编程实战
2. C专家编程

inotify机制在用户态下的使用方法

2011年9月24日

1.inotify是什么?

inotify机制用于监控文件系统,通过它可以监控一个或多个文件,如果该文件发生了指定事件,比如打开,读或写等,该机制会异步的响应用程序发出通知(或称为警告),应用程序根据文件系统发生的事件类型做出相应的反应。

2.inotify可以监控的事件

inotify使用一组宏来表示文件可以被监控的事件,这些宏在稍候介绍的inotify_add_watch()中使用。在没有特别说明的情况下,下面解释中的文件均指被监控的文件,并且即可以是普通文件又可以是目录文件。

IN_ACCESS:文件被访问,如果是目录文件,则指目录中的文件名被访问。

IN_MODIFY:文件被修改,如果是目录文件,则指目录中的文件名被修改。

IN_ATTRIB:文件属性被修改,比如使用chmod命令。

IN_CLOSE_WRITE:可写的文件被关闭。

IN_CLOSE_NOWRITE:不可写文件被关闭。

IN_CLOSE:文件被关闭,它等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)的效果。

IN_OPEN:文件被打开。

IN_MOVED_FROM:文件被移出监控区,比如使用mv命令将一个文件移出监控目录。

IN_MOVED_TO:文件(这个文件既可以是受监控的又可以是未受监控的)被移入监控区,比如使用mv和cp命令。

IN_MOVE:文件被移动,它等同于(IN_MOVED_FROM | IN_MOVED_TO)的作用效果。

IN_CREATE:在目录中创建一个新文件,比如touch或mkdir命令。

IN_DELETE:文件被删除,比如使用rm命令。

IN_DELETE_SELF:自删除,即一个可执行文件在执行时删除自己。

IN_MOVE_SELF:自移动,即一个可执行文件在执行时移动自己。

IN_UNMOUNT:宿主文件系统被 umount。

另外,IN_ISDIR宏用来判断被监控的文件是否为目录文件,该宏可以在应用程序对监控文件作监控处理时应用。

3.inotify用户态使用概述

inotify机制属于Linux在2.6.13之后增加的一个新特性,它属于dnotify机制的升级版。要使用inotify机制监控文件系统,那么必须先创建一个inotify的实例。由于Linux中的一切皆为文件,可以将inotify实例理解为一个“inotify类型的文件”,因此该实例会对应一个文件描述符,这也属于inotify优于dnotify的一大特性。

inotify机制的另一大特性即为监控程序对文件的监控不必轮询去查看,一旦监控的文件有指定的事件发生,它会异步通知监控程序,监控程序收到警告后会立马做出相应的响应。而在没有发生监控事件的时候,监控程序则一直处于阻塞状态。

这里的阻塞通过read()即可完成。当没有监控时间发生时,inotify实例中没有数据则read()阻塞;当有监控事件发生时,监控事件将被写入inotify实例中,此时read函数被唤醒读取该事件,监控程序根据读取的数据做出相应处理。这里的事件其实是通过字节流发送到inotify实例中的,因此可以通过read()函数来读取。为此,专门有一个数据结构来存储监控事件,即为struct inotify_event:

 struct inotify_event
struct inotify_event {
	__s32		wd;		/* watch descriptor */
	__u32		mask;		/* watch mask */
	__u32		cookie;		/* cookie to synchronize two events */
	__u32		len;		/* length (including nulls) of name */
	char		name[0];	/* stub for possible name */
}; 

该结构的定义位于用户态文件目录include/linux/inotify.h中,每个字段代表的含义如下:

wd:一个监视器(watch)的描述符,所谓监视器就是一个二元组(监视文件,事件掩码),其中事件掩码包含该文件被监视的所有事件。wd是通过inotify_add_watch()返回的,wd在此结构中与一个监视事件关联,即说明wd监视器上发生了当前inotify_event这个事件。

mask:该事件的类型即为当前结构中的mask,它是wd中所指定mask的一个子集。

len:表示当前结构中name的长度,但有时候name为了字节对齐会填充若干个0,因此len会大于等于name的长度。

name:表示监控文件的路径,这里通过GNU C中的0长度数组来表示变长的文件路径。

对inotify机制的典型使用方法如下:

1.创建并初始化一个inotify的实例,通过inotify_init()即可实现,该函数返回一个文件描述符。

2.添加一个或多个监控文件,即监视器,通过inotify_add_watch()即可实现,该函数就返回一个监视器的文件描述符。

3.循环等待监控事件的发生,通过循环read()inotify实例的fd即可实现。

4.如果有监控事件发生,则将fd中的字节流读取到inotify_event结构中,监控程序随之作适当处理,处理完毕后返回继续等待。

5.当不需要继续监控或收到某个代表监控结束的信号时,关闭inotify实例的文件描述符。

关于基本的使用流程还可以参考下图:

4.inotify用户态API

inotify的API都使用文件描述符,这样可以将监控粒度控制到单个文件,而dnotify机制的控制粒度则为单个目录。使用文件描述符更大的优势在于对inotify的操作也可以使用read()、close()、select()等这些传统的文件操作函数。

1.int inotify_init (void)

创建并初始化一个inotify实例,该函数返回一个文件描述符。可以认为这个函数是打开一个inotify类型的文件并返回该类型文件的描述符。

2.int inotify_add_watch (int __fd, const char *__name, uint32_t __mask)

增加监视文件(监视器),fd用于指明该文件被添加于哪个inotify实例,name用于指名该文件的路径,mask则指明了该文件所有的监控事件。该函数调用成功后返回一个监视器的描述符。

3.int inotify_rm_watch (int __fd, int __wd)

从fd中删除一个监视器,wd指名具体的监视器。

关于上述函数的详细的使用方法以及错误返回值等内容可以参考man手册。

参考:

IBM Developer Works:http://www.ibm.com/developerworks/cn/linux/l-ubuntu-inotify/index.html

Ubuntu下编译3.0.4内核

2011年9月8日

Linux内核3.0版本发布已有一段时间了,不知道这个版本号大跃进的内核是否好用。目前各个发行版的linux还仍未采用3.0的内核,因此可以自己动手编译内核来感受一下!趁着这次编译内核的机会可以再熟悉一下编译内核的步骤。

1.下载并解压内核到任意目录

从源码官网下载最新的内核源码3.0.4,可以解压至任意目录,我放在主目录下:

~$ tar xjvf linux-3.0.4.tar.bz2

2.配置内核

对内核进行配置是为了得到内核配置文件.config。通过对内核进行配置,可以使未来编译成功的内核增加或减少对一些内核特性的支持。对内核进行配置有多种方法,有基于文本的配置方式也有基于图形的用户界面。下面采用使用比较广泛的make menuconfig方式:

~/linux-3.0.4$sudo apt-get install libncurses5-dev
~/linux-3.0.4$sudo make menuconfig

由于该配置方式基于ncurses库,所以在启动配置界面前要先安装ncurses库。启动配置界面前,必须进入源码根目录,配置界面启动成功后如下图:

我们这里对内核按照默认的配置方式进行编译,因此当配置菜单启动后直接退出并保存即可。此时就在内核源码根目录下生成了.config文件。

3.编译

编译内核包含两部分的工作,其一是编译内核,即编译配置选项中标记为Y的那部分,这部分内核最终形成bzIamge镜像文件;其二是编译内核模块,即编译配置选项中标记为M的那部分内核,这部分形成以.ko结尾的内核模块目标文件。

上述两部分编译工作可以依次通过make bzImage和make modules完成,也可以通过一条make命令直接完成。编译内核的整个过程比较漫长,因此可以对make加-j参数来提高编译的效率。在make时使用该选项会为编译过程分配n个并发任务,这样可以缩短编译时间。n的取值为cpu个数的二倍。

~/linux-3.0.4$sudo make -j4

4.安装

安装过程分为两部分,首先对内核模块进行安装,这个过程会将刚刚编译内核模块时生成的内核模块复制到/lib/modules/3.0.4/目录下,其中3.0.4为对应的内核版本。使用的命令如下:

~/linux-3.0.4$sudo make modules_install

接着使用下述命令安装编译好的内核:

~/linux-3.0.4$sudo make install

安装内核的过程主要完成了以下的工作:

1.将编译内核时生成的内核镜像bzImage拷贝到/boot目录下,并将这个镜像命名为vmlinuz-3.0.4。如果使用x86的cpu,则该镜像位于arch/x86/boot/目录下(处于正在编译的内核源码下)。

2.将~/linux-3.0.4/目录下的System.map拷贝到/boot/目录下,重新命名为System.map-3.0.4。该文件中存放了内核的符号表。

3.将~/linux-3.0.4/目录下的.config拷贝到/boot/目录下,重新命名为config-3.0.4。

5.创建initrd.img文件

initrd.img即为初始化的ramdisk文件,它是一个镜像文件,将一些最基本的驱动程序和命令工具打包到镜像文件里。该镜像文件的作用是在系统还没有挂载根分区前,系统需要执行一些操作,比如挂载scsi驱动,此时将initrd文件释放到内存中,作为一个虚拟的根分区,然后执行相关脚本,运行insmod命令加载需要的模块。

具体的创建方法如下:

~/linux-3.0.4$sudo mkinitramfs 3.0.4 -o /boot/initrd.img-3.0.4

6.更新grub

最后一步则是更新grub启动菜单,使用下面的命令则可以自动更新启动菜单:

sudo update-grub2

这样会将刚才编译好的内核放在启动菜单的首位,如果需要修改启动菜单中默认系统的启动顺序,则修改/boot/grub/grub.cfg文件中的set default=的值即可。

OK,内核编译完毕。

在/etc/passwd中得到普通用户列表

2011年9月7日

/etc/passwd文件用来保存系统中当前所有的用户信息,该文件对所有用户都可见。在该文件中,每行信息代表一个用户。每个用户的信息由7部分组成:

用户名:加密后的用户密码:用户ID(UID):用户所在组ID(GID):用户全名以及用户信息:用户主目录:该用户登录时所用的命令解释器

在该文件中,有些用户并不是用户建立的,而是系统帐号。比如在上述passwd文件中,sys、bin等用户就是系统所保留的用户。

edsionte@edsionte-desktop:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
…………
guest:x:1001:1001:guest,,,,:/home/guest:/bin/bash
lc:x:1002:1002:lc,,,,:/home/lc:/bin/bash

现在有一个项目需求,即从/etc/passwd文件中将普通用户的用户名解析出来。要完成这个要求,关键是找出系统用户和普通用户之间的差别。在useradd命令的man手册中关于-u选项有这样的描述:

-u uid 使 用 者 的 ID 值 。 必 须 为 唯 一 的 ID 值 , 除 非 用 -o 选 项 。 数 字 不 可 为 负 值。预设 为 最 小 不 得 小 于 999 而 逐 次 增 加 。 0~ 999 传 统 上 是 保 留 给 系 统 帐 号 使 用。

因此,可以通过UID来区分普通用户和系统用户,即UID大于999的为普通用户,否则为系统用户。

后记:本文发出后,有几个朋友指出这个办法只适合ubuntu系统,因此希望大家注意使用范围。

线程那些事儿(2)-实践

2011年9月5日

在多线程程序中,一个新的线程通常由一个进程调用phtread_create()函数而诞生的。新线程创建后,通常将这个进程称为主线程。你也许会有所迷惑:一个进程怎么会编程线程?此刻有几个线程,几个进程?

其实通过上文对线程、轻量级进程以及线程组之间关系的理解后,这个问题似乎也不难回答。我们可以将所有的进程都看作一个线程组,只不过普通进程的线程组只包含它自己一个线程,它不能与其他线程共享资源,只能独享自己的资源(而成为进程)。

对于多线程程序来说,一旦在进程内创建了一个线程,那么该进程也就摇身变成了一个线程。主线程和子线程共享“以前”那个进程所独享的资源。主线程和子线程之间是并列关系,不存在类似fork()函数那样的父子进程关系,这也就是不将创建线程的进程称为父线程的原因。

如果你还对上述的描述有所疑惑,那么通过下面的实验结果可以理解的更加深刻。下述的程序就是一个普通的线程创建,只不过主线程和子线程增加了延时以方便我们查看实验结果。

int *thread(void* arg)
{
	pthread_t newthid;
	newthid = pthread_self();//get the current thread's id
	printf("this a new thread and thread ID is=%lu\n", newthid);
	sleep(500);
	return NULL;
}

int main()
{
	pthread_t thid;

	printf("main thread,ID is %lu\n", pthread_self());//get the main thread's id
	if (pthread_create(&thid, NULL, (void *)thread, NULL) != 0) {
		printf("thread creation failed\n");
		exit(1);
	}

	printf("my Id is %lu, new thread ID is %lu\n", pthread_self(), thid);
	sleep(1000);
	return 0;
}

我们带开一个终端(称为终端1)运行上述程序,再另一个终端(称为终端2)里使用ps -eLf命令查看系统当前的线程信息。

UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
edsionte  2210  2208  2210  0    1 09:10 pts/0    00:00:00 bash
edsionte  2429  2210  2429  0    2 09:52 pts/0    00:00:00 ./createthread
edsionte  2429  2210  2430  0    2 09:52 pts/0    00:00:00 ./createthread
edsionte  2431  2208  2431  5    1 09:52 pts/1    00:00:00 bash
edsionte  2449  2431  2449  0    1 09:52 pts/1    00:00:00 ps -eLf

请注意上述信息中三类ID信息:PID,PPID和LWP。LWP是轻量级进程的pid,NLWP为线程组中线程的个数。下面对上述的实验结果作以解释。

1.运行实验程序的终端对应的pid为2210;

2.我们的实验程序产生了两个线程,其pid都是2429。这说明这两个线程是并列关系,它们属于同一个线程组,该线程组的pid为2429。

3.实验程序产生的两个线程的PPID均为2210,再次说明这两个线程之间没有父子关系,他们的父亲均为终端1对应的进程。

4.每个线程都与一个轻量级进程关联,因此两个线程的LWP不同,分别为2429和2430。

5.这两个线程形成一个线程组,因此他们对应的NLWP为2。

6.通过pid,ppid和LWP的分配情况可以看到,内核对于进程和轻量级进程的id分配是统一管理的,这源于他们使用相同的数据结构task_struct。

上述分析基本上用实验结果诠释了进程、线程和轻量级进程之间的关系。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key