Chinaunix首页 | 论坛 | 博客
  • 博客访问: 760310
  • 博文数量: 230
  • 博客积分: 6330
  • 博客等级: 准将
  • 技术积分: 2188
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-10 15:55
个人简介

脚踏实地

文章分类

全部博文(230)

文章存档

2017年(1)

2016年(7)

2015年(10)

2014年(32)

2013年(24)

2012年(33)

2011年(50)

2010年(30)

2009年(43)

分类: LINUX

2010-08-20 17:48:00

进程是资源分配的最小单位,线程是CPU调度的最小单位。

一:关于他们的ID:
pid is process identifier; tid is thread identifier 

gettid vs pthread_self

POSIX thread IDs are not the same as the thread IDs returned by the Linux-specific gettid() system call. POSIX thread IDs are assigned and maintained by the threading implementation. The thread ID returned by gettid() is a number (similar to a process ID) that is assigned by the kernel.

I would go with pthread_setaffinity_np but be aware that the manual says:
These functions are implemented on top of the sched_setaffinity(2)

gettid()  returns  the  caller's  thread  ID  (TID).   In a single-threaded process, the thread ID is equal to the process ID (PID, as returned by getpid(2)).  In a multithreaded process, all threads have the same PID,  but  each one has a unique TID.

线程拥有自己的堆栈,和自己的寄存器上下文切换时,需要挂起线程,存储被切换线程的堆栈和寄存器上下文,然后设置新的线程的寄存器上下文和堆栈,调整这两个线程的级别。

因为这个属于线程的堆栈非常小,所以才认为:进程是资源分配的最小单位

二: 进程和线程的状态
线程一般有3/4种,而进程有很多种。
线程:
就绪:线程分配了CPU以外的全部资源,等待获得CPU调度
执行:线程获得CPU,正在执行
阻塞:线程由于发生I/O或者其他的操作导致无法继续执行,就放弃处理机,转入线程就绪队列
第四种:
挂起:由于终端请求,操作系统的要求等原因,导致挂起。

进程:
1、TASK_RUNNING:
进程当前正在运行,或者正在运行队列中等待调度。
2、TASK_INTERRUPTIBLE:
进程处于睡眠状态,正在等待某些事件发生。进程可以被信号中断。接收到信号或被显式的唤醒呼叫唤醒之后,进程将转变为 TASK_RUNNING状态。
3、TASK_UNINTERRUPTIBLE:
此进程状态类似于 TASK_INTERRUPTIBLE,只是它不会处理信号。中断处于这种状态的进程是不合适的,因为它可能正在完成某些重要的任务。 当它所等待的事件发生时,进程将被显式的唤醒呼叫唤醒。
4、TASK_STOPPED:
进程已中止执行,它没有运行,并且不能运行。接收到 SIGSTOP 和 SIGTSTP 等信号时,进程将进入这种状态。接收到 SIGCONT 信号之后,进程将再次变得可运行。
5、TASK_TRACED:
正被调试程序等其他进程监控时,进程将进入这种状态。
6、EXIT_ZOMBIE:
进程已终止,它正等待其父进程收集关于它的一些统计信息。
7、EXIT_DEAD:
最终状态(正如其名)。将进程从系统中删除时,它将进入此状态,因为其父进程已经通过 wait4() 或 waitpid() 调用收集了所有统计信息。
8、TASK_KILLABLE:
Linux? kernel 2.6.25 引入了这种进程状态,用于将进程置为睡眠状态,它可以替代有效但可能无法终止的 TASK_UNINTERRUPTIBLE 进程状态,以及易于唤醒但更加安全的 TASK_INTERRUPTIBLE 进程状态。 


linux2.6.26中的代码:

点击(此处)折叠或打开

  1. * We have two separate sets of flags: task->state
  2.  * is about runnability, while task->exit_state are
  3.  * about the task exiting. Confusing, but this way
  4.  * modifying one set can't modify the other one by
  5.  * mistake.
  6.  */
  7. #define TASK_RUNNING        0
  8. #define TASK_INTERRUPTIBLE    1
  9. #define TASK_UNINTERRUPTIBLE    2
  10. #define __TASK_STOPPED        4
  11. #define __TASK_TRACED        8
  12. /* in tsk->exit_state */
  13. #define EXIT_ZOMBIE        16
  14. #define EXIT_DEAD        32
  15. /* in tsk->state again */
  16. #define TASK_DEAD        64
  17. #define TASK_WAKEKILL        128

  18. /* Convenience macros for the sake of set_task_state */
  19. #define TASK_KILLABLE        (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
  20. #define TASK_STOPPED        (TASK_WAKEKILL | __TASK_STOPPED)
  21. #define TASK_TRACED        (TASK_WAKEKILL | __TASK_TRACED)

三:关于Linux轻量级进程的缘由:【LKD 3.3节】
linux的进程本身就是轻量级的。感觉主要是 子进程会共享父进程的很多资源,比如页表。

关于页表:
page table is the data structure used by a virtual memory system in a computer operating system to store the mapping between virtual addressesand physical addresses. Virtual addresses are those unique to the accessing process. Physical addresses are those unique to the hardware, i.e., RAM.

四: 信号量 和 互斥量
semaphore也可以用于线程间共享资源的保护,但mutex却不能用于进程间共享资源的保护,因为两个进程不可能共用一个mutex
前者偏  同步, 后者偏 互斥

例子:http://blog.chinaunix.net/uid-20791902-id-2142050.html

两者之间的区别:

·作用域
信号量: 进程间或线程间(linux仅线程间)
互斥锁: 线程间

·上锁时 
信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait阻塞,直到sem_post释放后value值加一。一句话,信号量的value>=0
互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源。如果没有锁,获得资源成功,否则进行阻塞等待资源可用。一句话,线程互斥锁的vlaue可以为负数


==========

semGive(semSync);   //信号量释放,有效。

semTake(semSync,WAIT_FOREVER);  //等待信号量

使用二进制信号量可以很方便的实现互斥,互斥是指多任务在访问临界资源时具有排他性。为使多个任务互斥访问临界资源,只需要为该资源设置一个信号量,相当 于一个令牌,哪个任务拿到这个令牌即有权使用该资源。把信号量设为可用,然后将需要资源的任务的临界代码置于semTake()和SemGive()之间 即可。

同步即任务按照一定顺序先后执行,为了实现任务A和B的同步,只需要让任务A和B共享一个信号量,并设初始值为空,即不可用,将semGive()置于任务A之后,而在任务B之前插入semTake()即可.

--思考--
while(1){
semTake(SEMID);( u# u7 o: R3 ?* w
1st statement;# x0 ?. `; Q1 N3 y1 O
2nd statement;
3rd statement;- X2 |6 e4 {8 V# I& o
}! E6 a, \& w: x5 t% h
问个问题:如果上面的task执行到2nd statement时,下一个semGive(SEMID)到来,上面这个task还会相应这个semGive(SEMID)吗?
不会立即响应,在执行完3rd statement返回到semTake(SEMID)才响应
下面的图来自:http://software.intel.com/zh-cn/blogs/2010/07/20/400004478/?cid=sw:prccsdn1223




Unix中有两类特殊的进程:孤儿进程orphan和僵尸进程zombie

Orphan:当父进程比子进程先终止时,子进程的将被init进程接管,

Zombie:当子进程终止之后(exit仅仅是将进程的状态改变),父进程没有获取子进程的退出状态时(watipid),内核进程空间表中还存在子进程的记录,资源也未被释放。(只有父进程获取子进程终止状态之后,内核才会从进程空间表中将子进程记录删除并释放资源)


僵尸进程的处理
由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 那么不会因为父进程太忙来不及wait子进程,或者说不知道 子进程什么时候结束,而丢失子进程结束时的状态信息呢? 

不会.因为UNIX提供了一种机制可以保证 只要父进程想知道子进程结束时的状态信息, 就可以得到. 这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等. 但是
仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等), 直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果你进程不调用wait / waitpid的话, 那么保留的那段信息就不会 释放,其进程号就会一定被占用,但是系统所能使用的进程号是有限的,如果大量的产生 僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免. 

僵尸进程的避免 1、父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起 2. 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后, 父进程会收到该信号,可以在handler中调用wait回收 3. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号 4. 还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收 还要自己做。

它需要它的父进程来为它收尸,如果他的父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信 号,那么它就一直保持僵尸状态; 存在的问题:如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程,系统的性能可能会收到影响。 ** 如果这时父进程结束了,那么自 动会接手这个子进程,为它收尸,它还是能被清除的。

4、子进程结束后为什么要进入僵尸状态?
* 因为父进程可能要取得子进程的退出状态等信息。

5、僵尸状态是每个子进程比经过的状态吗?
是的。
* 任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个 子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。
* 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。 


6、如何查看僵尸进程
$ ps -el 其中,有标记为Z的进程就是僵尸进程 S代表休眠状态;D代表不可中断的休眠状态;R代表运行状态;Z代表僵死状态;T代表停止或跟踪状态

7.如何查看孤儿进程??

问10.10.10.126/25和10.10.10.10.127/25是否可以通信,感觉主考官是在考察对单播地址和广播地址的考察,两个单播地址如 果同一网段走二层转发就可以了,如果不同网段有路由的话走三层转就可以了,但这些不是主考官想问的。10.10.10.10.127/25掩码25位的情 况下,这个地址就成了广播地址(最后7bit全是1)。感觉这是他给的陷阱。



8. linux中的进程0 和 进程1

1. 进程0是所有其他进程的祖先也称作idle进程或swapper进程。

2. 进程0是在系统初始化时由kernel自身从无到有创建。(过程集中在start_kernel函数内)

3. 进程0的数据成员大部分是静态定义的,即由预先定义好的(如上)INIT_TASK, INIT_MM等宏初始化。

 

进程0的描述符init_task定义在arch/arm/kernel/init_task.c,INIT_TASK宏初始化。 init_mm等结构体定义在include/linux/init_task.h内,为init_task成员的初始值,分别由对应的初始化宏如INIT_MM等初始化,


进程1: 

 

进程0最终会通过调用kernel_thread创建一个内核线程去执行init函数,这个新创建的内核线程即Process 1(这时还是共享着内核线程0的资源属性如地址空间等)init函数继续完成剩余的内核初始化,并在函数的最后调用execve系统调用装入用户空间的可执行程序/sbin/init,这时进程1就拥有了自己的属性资源,成为一个普通进程(init进程)


9  读写锁   一个细节:来自【1】
如果在写者锁等待队列中有一个或多个写线程正在等待获得写者锁时,新加入的读线程会被放入读者锁的等待队列。这是因为,尽管这个新加入的读线程能与正在进行读操作的那些读线程并发读取共享资源,但是也不能赋予他们读权限,这样就防止了写线程被新到来的读线程无休止的阻塞。

10 粗粒度锁与细粒度锁 来自【1】
为了减少串行部分的执行时间,我们可以通过把单个锁拆成多个锁的办法来较小临界区的执行时间,从而降低锁竞争的性能损耗,即把“粗粒度锁”转换成“细粒度锁”。但是,细粒度锁并不一定更好。这是因为粗粒度锁编程简单,不易出现死锁等Bug,而细粒度锁编程复杂,容易出错;而且锁的使用是有开销的(例如一个加锁操作一般需要100个CPU时钟周期),使用多个细粒度的锁无疑会增加加锁解锁操作的开销。
在计算机系统设计领域,没有哪种设计是没有缺点的,需要具体对待。例如,Linux内核在初期使用了Big Kernel Lock(粗粒度锁)来实现并行化。从性能上来讲,使用一个大锁把所有操作都保护起来无疑带来了很大的性能损失,但是它却极大的简化了并行整个内核的难度。当然,随着Linux内核的发展,Big Kernel Lock已经逐渐消失并被细粒度锁而取代,以取得更好的性能。

11. linux中进程的优先级

默认值为80,要想更改,通过命令renice
例子:
ian@attic4:~$ renice +3 1537;ps -l 1537
1537: old priority 1, new priority 3
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY        TIME CMD
0 R  1000  1537 32408 99  83   3 -  1001 -      pts/3      0:18 sh count1.sh 100
vxworks内核有256个优先级,编号是0-255,0的优先级最高,255最低。任务的优先级可以在创建时设定,系统默认的任务优先级为100。

12


========CSDN上的一个关于进程,线程堆栈区别的分析=======

很 久很久以前,是没有进程这个东西的。那时候的操作系统只能把要做的工作排好队,做完这件再做下一件,最多也就加个优先级,哪个关系好就先做哪一个。于是那 些想一边听歌一边写程序的程序员们便不干了,开始修理那个操作系统,使之能同时运行多个程序。于是进程就出现了:它就是一个程序在数据集合上的一次执行。
 

因为突然变得僧多粥少了,所以每个进程只好做一些额外的事情:在别人使用之前把自己的东西收拾好,下次轮到自己时再摆出来。为了保存这些额外的东西,进程的结构也发生了相应的变化。一个进程被分成三大部分:代码段、数据段和PCB(进程控制块)。

在多出来的PCB中,我们保存了如下信息:
l   进程标识符(操作系统用于识别进程的唯一标识)

l    处理机状态(主要是通用寄存器,指令寄存器,PSW和用户栈指针)

l    进程调度信息(状态、优先级,被阻塞原因和其他一些乱七八糟的东西)

l    进程控制信息(同步信息、代码段和数据段的信息、资源清单和指向下一个PCB的指针)

 

操作系统正是通过PCB来管理这多个进程。在这样的系统里,进程既是操作系统独立调度和分派的基本单位,又是一个可拥有资源的独立单位。

 

线程:进程的再分身
 

好 了,现在程序员可以一般听歌一边写程序了。可是不幸的或者说是幸运的,新的问题出现了。如果多个进程间使用很多相同数据的话,实在是太浪费了。我们当然不 能允许这样的事情持续下去,所以线程出现了。同一个进程下可以拥有多个线程,它们共享这个进程的资源,它们之间的切换也不再需要PCB,而只需要极少一点 资源就可以了。在这样的操作系统里,线程变成了系统调度和分派的基本单位。 

简单的说进程和线程有如下不同:


l进程可以拥有资源,线程共享进程拥有的资源

l进程间的切换必须保存PCB,同个进程的多个线程间的切换不用那么麻烦

 最后我们以一个实例来作为本文的结束:

当你在一台PC上打开两个QQ时,每一个QQ是一个进程;而当你在一个QQ上和多人聊天时,每一个聊天窗口就是一个线程。

& y0 Q0 a X. i" c

===============
【线程】是一组特定的指令序列的实体。它可以被操作系统安排 
去运行(调度)。在单CPU系统上,很显然多个线程不可能 
真正同时运行,只能按照某种顺序依次轮流运行
。这个顺序是 
由操作系统的调度算法决定的。 

线程在其存在期间有状态,例如: 
运行状态:正在运行; 
ready状态:等待操作系统安排它去运行; 
阻塞状态:等待特定事件发生,例如I/O操作完成等; 
睡眠状态:等待一个定时器超时。 

线程只有在运行状态才会消耗CPU时间。在其它状态时,仅 
需要调度程序的管理。 

线程拥有自己的CPU寄存器和堆栈。在它占用CPU运行时, 
如果OS的调度程序决定‘赶走’它,系统会保存CPU寄存器到 
一个和这个线程有关的数据结构中;如果OS决定让某线程 
再次运行,则OS从该数据结构中恢复CPU寄存器。这样达到 
切换的目的。 


有些操作系统如VxWorks,没有进程和线程的概念, 
只有任务。任务之于OS,大致相当于线程之于进程。所有 
任务,包括OS,共享系统中唯一的地址空间。所有任务共享 
系统中所有资源。一个任务打开一个设备后,别的任务可以 
向这个设备上读或写。 

然而还是有些不同的。例如,进程中的线程,它们所打开的 
文件描述符会被记录在该进程的进程控制块(PCB)中,这样, 
当进程终止时,OS会去关闭这些文件(如果还没关闭的话)。 
然而在VxWorks中,别指望在OS关机前给你关闭文件要显式调用close函数???】,所以 
可能出现文件数据丢失(如果任务不关闭该文件)。 

参考:
【1】
阅读(1813) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~