您的位置:首页 > 运维架构 > Linux

linux 2.6进程与线程

2013-07-01 09:58 281 查看
1 >    线程和进程的差别

线程机制支持并发程序设计技术,在多处理器上能真正保证并行处理。而在linux实现线程很特别,linux把所有的线程都当作线程实现。

linux下线程看起来就像普通进程(只是该进程和其他进程共享资源,如地址空间)。上述机制与Microsoft windows或是Sun Solaris实现

差异很大。这些系统提供专门支持线程机制(轻量级进程)。

在现代操作系统中,进程支持多线程。进程是资源管理及分配的最小单元;而线程是程序执行的最小单元。一个进程的组成实体可以分为两大部分:线程集和资源集。进程中的线程是动态的对象,代表了进程指令的执行过程。资源,包括地址空间、打开的文件、用户信息等等,由进程内的线程共享。线程有自己的私有数据:程序计数器,栈空间以及寄存器。

现实中有很多需要并发处理的任务,如数据库的服务器端、网络服务器、大容量计算等。

如果采用多进程的方法,则有如下问题:

      1> fork一个子进程的消耗是很大的,fork是一个昂贵的系统调用。

      2> 各个进程拥有自己独立的地址空间,进程间的协作需要复杂的IPC技术,如消息传递和共享内存等。

线程推广了进程的概念,使一个进程可以包含多个活动(或者说执行序列等等)。多线程的优点和缺点实际上是对立统一的。使用线程的优点在于:

      1> 改进程序的实时响应能力;

      2> 更有效的使用多处理器,真正的并行(parallelism);

      3> 改进程序结构,具备多个控制流;

      4> 通讯方便,由于共享进程的代码和全局数据;

      5> 减少对系统资源的使用。对属于同一个进程的线程之间进行调度切换时不需要调用系统调用,因此将减少额外的消耗,往往一个进程可以启动上千个线程也没有什么问题。

缺点在于:

由于各线程共享进程的地址空间,因此可能会导致竞争,因此对某一块有多个线程要访问的数据需要一些同步技术。

2 >     线程的分类

2.1     内核线程

Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进程的种种合理以及不合理的请求)。内核需要多个执行流并行,为了防止可能的阻塞,多线程化是必要的。内核线程就是内核的分身,一个分身可以处理一件特定事情。Linux内核使用内核线程来将内核分成几个功能模块,像kswapd、 kflushd等,这在处理异步事件如异步IO时特别有用。内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。支持多线程的内核叫做多线程内核(Multi-Threads kernel )。内核线程的调度由内核负责,一个内核线程处于阻塞状态时不影响其他的内核线程,因为其是调度的基本单位。这与用户线程是不一样的。

2.2用户线程

用户线程在用户空间中实现,内核并没有直接对用户线程进程调度,内核的调度对象和传统进程一样,还是进程本身,内核并不知道用户线程的存在。

由于Linux内核没有轻量级进程(线程)的概念,因此不能独立的对用户线程进行调度,而是由一个线程运行库来组织线程的调度,其主要工作在于在各个线程的栈之间调度。如果一个进程中的某一个线程调用了一个阻塞的系统调用,整个进程就会被调度程序切换为等待状态,其他线程得不到运行的机会。因此linux 使用了异步I/O机制。

3 >     内核线程

内核线程(thread)或叫守护进程(daemon),在操作系统中占据相当大的比例,当Linux操作系统启动以后,尤其是X window也启动以后,你可以用”ps -ef”命令查看系统中的进程,这时会发现很多以”d”结尾的进程名,确切说名称显示里面加 "[]"的,这些进程就是内核线程。系统的启动是从硬件->内核->用户态进程的,pid的分配是一个往前的循环的过程,所以随系统启动的内核线程的pid往往很小。

UID        PID  PPID  C STIME TTY          TIME CMD

root         1     0  0 09:42 ?        00:00:01 /sbin/init

root         2     0  0 09:42 ?        00:00:00 [kthreadd]

root         3     2  0 09:42 ?        00:00:00 [migration/0]

root         4     2  0 09:42 ?        00:00:00 [ksoftirqd/0]

root         5     2  0 09:42 ?        00:00:00 [watchdog/0]

root         6     2  0 09:42 ?        00:00:00 [events/0]

root         7     2  0 09:42 ?        00:00:00 [khelper]

root        40     2  0 09:42 ?        00:00:00 [kblockd/0]

root        64     2  0 09:42 ?        00:00:00 [kseriod]

root       110     2  0 09:42 ?        00:00:00 [pdflush]

root       111     2  0 09:42 ?        00:00:00 [pdflush]

root       112     2  0 09:42 ?        00:00:00 [kswapd0]

root       151     2  0 09:42 ?        00:00:00 [aio/0]

root      1327     2  0 09:42 ?        00:00:00 [ksuspend_usbd]

root      1330     2  0 09:42 ?        00:00:00 [khubd]

root      1362     2  0 09:42 ?        00:00:00 [ata/0]

root      1370     2  0 09:42 ?        00:00:00 [ata_aux]

root      1428     2  0 09:42 ?        00:00:00 [scsi_eh_0]

root      1430     2  0 09:42 ?        00:00:01 [scsi_eh_1]

root      2304     2  0 09:42 ?        00:00:00 [kjournald]

root      2507     1  0 09:42 ?        00:00:00 /sbin/udevd --daemon

root      2787     2  0 09:42 ?        00:00:00 [kgameportd]

root      3698     2  0 09:42 ?        00:00:00 [kapmd]

root      3872     2  0 09:42 ?        00:00:00 [kjournald]

3.1     内核线程

      events 处理内核事件很多软硬件事件(比如断电,文件变更)被转换为events,并分发给对相应事件感兴趣的线程进行响应

      ksoftirqd 处理软中断硬件中断处理往往需要关中断,而这个时间不能太长,否则会丢失新的中断。所以中断处理的很大一部分工作移出,转给任劳任怨的ksoftirqd在中断之外进行处理。比如一个网络包,从网卡里面取出这个过程可能需要关中断,但是TCP/IP协议处理就不必关中断了

      kblockd 管理磁盘块读写

      kjournald Ext3文件系统的日志管理 通常每个 _已mount_ 的 Ext3分区会有一个 kjournald看管,各分区的日志是独立

      pdflush dirty内存页面的回写太多dirty的页面意味着风险,比如故障时候的内容丢失,以及对突发的大量物理内存请求的响应(大量回写会导致糟糕的响应时间)

      kswapd 内存回收 确保系统空闲物理内存的数量在一个合适的范围

      aio 代替用户进程管理io 用以支持用户态的AIO

      

3.2     用户进程

      crond 执行定时任务

      init 为内核创建的第一个线程。引导用户空间服务,管理孤儿线程,以及运行级别的转换

      

      mingetty 等待用户从tty登录

      bash shell进程,一个命令行形式的系统接口;接受用户的命令,并进行解释、执

      sshd ssh登录、文件传输、命令执行 等操作的服务进程

      klogd 从内核信息缓冲区获取打印信息。内核在发现异常的时候,往往会输出一些消息给用户,这个对于故障处理很有用

      syslogd 系统日志进程

      udevd 支持用户态设备操作 (userspace device)

      

      

4>     创建线程 clone_flags   

最初的进程定义都包含程序、资源及其执行三部分,其中程序通常指代码,资源在操作系统层面上通常包括内存资源、IO资源、信号处理等部分,而程序的执行通常理解为执行上下文,包括对cpu的占用,后来发展为线程。在线程概念出现以前,为了减小进程切换的开销,操作系统设计者逐渐修正进程的概念,逐渐允许将进程所占有的资源从其主体剥离出来,允许某些进程共享一部分资源,例如文件、信号,数据内存,甚至代码。应用程序可以通过一个统一的clone()系统调用接口,用不同的参数指定创建轻量进程还是普通进程。在内核中,clone()调用经过参数传递和解释后会调用do_fork(),这个核内函数同时也是
fork():

long do_fork(unsigned long                   clone_flags,                                                                                                                    unsigned long stack_start,

struct pt_regs *regs,

unsigned long stack_size,

int __user *parent_tidptr,

int __user *child_tidptr)

其中的clone_flags取自以下宏的"或"值:

在 do_fork()中,不同的clone_flags将导致不同的行为,对于LinuxThreads,它使用(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)参数来调用clone()创建"线程",表示共享内存、共享文件系统访问计数、共享文件描述符表,以及共享信号处理方式。本节就针对这几个参数,看看Linux内核是如何实现这些资源的共享的。

1.CLONE_VM

do_fork()需要调用copy_mm()来设置task_struct中的mm和active_mm项,这两个mm_struct数据与进程所关联的内存空间相对应。如果do_fork()时指定了CLONE_VM开关,copy_mm()将把新的task_struct中的mm和 active_mm设置成与 current的相同,同时提高该mm_struct的使用者数目(mm_struct::mm_users)。也就是说,轻量级进程与父进程共享内存地址空间,由下图示意可以看出mm_struct在进程中的地位:

2.CLONE_FS

task_struct 中利用fs(struct fs_struct *)记录了进程所在文件系统的根目录和当前目录信息,do_fork()时调用copy_fs()复制了这个结构;而对于轻量级进程则仅增加 fs->count计数,与父进程共享相同的fs_struct。也就是说,轻量级进程没有独立的文件系统相关的信息,进程中任何一个线程改变当前目录、根目录等信息都将直接影响到其他线程。

3.CLONE_FILES

一个进程可能打开了一些文件,在进程结构 task_struct中利用files(struct files_struct *)来保存进程打开的文件结构(struct file)信息,do_fork()中调用了copy_files()来处理这个进程属性;轻量级进程与父进程是共享该结构的,copy_files() 时仅增加files->count计数。这一共享使得任何线程都能访问进程所维护的打开文件,对它们的操作会直接反映到进程中的其他线程。

4.CLONE_SIGHAND

每一个Linux进程都可以自行定义对信号的处理方式,在task_struct中的sig(struct signal_struct)中使用一个struct k_sigaction结构的数组来保存这个配置信息,do_fork()中的copy_sighand()负责复制该信息;轻量级进程不进行复制,而仅仅增加signal_struct::count计数,与父进程共享该结构。也就是说,子进程与父进程的信号处理方式完全相同,而且可以相互更改。

尽管Linux支持轻量级进程,但并不能说它就支持核心级线程,因为Linux的"线程"和"进程"实际上处于一个调度层次,共享一个进程标识符空间,这种限制使得不可能在Linux上实现完全意义上的POSIX线程机制,因此众多的Linux线程库实现尝试都只能尽可能实现POSIX的绝大部分语义,并在功能上尽可能逼近。

 

对内核线程的虚拟空间总结一下:

1、创建的时候:

父进程是用户进程,则mm和active_mm均共享父进程的,然后内核线程一般调用daemonize适头舖m

父进程是内核线程,则mm和active_mm均为NULL

总之,内核线程的mm = NULL;进程调度的时候以此为依据判断是用户进程还是内核线程。

2、进程调度的时候

如果切换进来的是内核线程,则置active_mm为切换出去的进程的active_mm;

如果切换出去的是内核线程,则置active_mm为NULL。

linux在创建用户任务的时候,给每个任务都分配了一个kernel mode stack。一个运行在用户态的任务如果被一个IRQ打断,中断处理要做一次堆栈切换。这时linux好像使用了任务的kernel mode stack,也就是说linux系统中没有一个唯一的系统堆栈,而是每一个任务都有一个系统堆栈,中断处理的栈使用的就是被打断任务的系统堆栈。内核线程也是进程,只不过没有自己的用户空间,但task_struct和内核堆栈还是得有的,要不怎么运行呢?

[1.内核在主动进行进程调度时,可以自己设置将要投入运行进程的sp0为TSS段中的sp0,则该用户进程在进入内核后使用的是它自身的系统堆栈,但如果cpu运行在某一用户进程时,而为另一用户进程服务的外部中断发生了,在进入内核后使用的是当前用户进程的系统堆栈,还是中断服务的另一用户进程的系统堆栈呢?

2操作系统映象是否拥有自己的堆栈空间?还是利用用户进程的系统堆栈?

回答:  1。外部中断不是为某个用户进程服务的,是为整个操作系统服务的,它始终用当前进程的核心堆栈。 

2。用用户进程的系统堆栈。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux 进程 线程