进程
2016-03-19 16:42
232 查看
一、进程的存储器安排
这是《UNIX环境高级编程》中介绍的典型存储器安排,但现实也不一定非要如此。对于c/c++来说,数据的存储方式还是认为3种:堆、栈、全局数据区(包括全局数据、静态数据、常量)。
二、僵尸进程和孤儿进程
进程在终止前向父进程发送SIGCLD信号,父进程调用wait等待子进程的退出!
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程(ps进程状态为“Z”)。
孤儿进程没有什么危害,因为将来结束时,init进程会完成他们状态收集的工作。而僵尸进程在内核中还有一些信息(包括进程号),需要及时处理;但是每个进程都要或长或短地经历僵尸进程的状态。
三、fork和vfork
调用fork一次返回2次,分别在父进程和子进程中返回,父进程中其返回值是子进程的进程标识符,子进程中其返回值是0。
fork创建一个进程时,子进程复制父进程的数据段,堆栈,共享父进程的代码段。但是为了降低开销,现在Linux中是采取了copy-on-write(COW写时复制)技术,只有在写入的时候,才复制写入的那块内存,一般为虚拟内存的一“页”(即部分复制)。
vfork,1、保证子进程先运行,即在调用exec和exit之前父进程不运行,也就是如果子进程依赖父进程的进一步动作,将陷入死锁。2、在调用exec和exit之前,子进程和父进程是共用数据段和代码段的,也就是子进程的数据更改会影响父进程。
四、进程间的通信
管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。
(1) 信号
---- 信号机制是UNIX为进程中断处理而设置的。它只是一组预定义的值,因此不能用于信息交换,仅用于进程中断控制。例如在发生浮点错、非法内存访问、执行无 效指令、某些按键(如ctrl-c、del等)等都会产生一个信号,操作系统就会调用有关的系统调用或用户定义的处理过程来处理。
---- 信号处理的系统调用是signal,调用形式是:
---- signal(signalno,action)
---- 其中,signalno是规定信号编号的值,action指明当特定的信号发生时所执行的动作。
(2) 无名管道和有名管道
---- 无名管道实际上是内存中的一个临时存储区,它由系统安全控制,并且独立于创建它的进程的内存区。管道对数据采用先进先出方式管理,并严格按顺序操作,例如不能对管道进行搜索,管道中的信息只能读一次。
---- 无名管道只能用于两个相互协作的进程之间的通信,并且访问无名管道的进程必须有共同的祖先。
---- 系统提供了许多标准管道库函数,如:
pipe()——打开一个可以读写的管道;
close()——关闭相应的管道;
read()——从管道中读取字符;
write()——向管道中写入字符;
---- 有名管道的操作和无名管道类似,不同的地方在于使用有名管道的进程不需要具有共同的祖先,其它进程,只要知道该管道的名字,就可以访问它。管道非常适合进程之间快速交换信息。
(3) 消息队列(MQ)
---- 消息队列是内存中独立于生成它的进程的一段存储区,一旦创建消息队列,任何进程,只要具有正确的的访问权限,都可以访问消息队列,消息队列非常适合于在进程间交换短信息。
---- 消息队列的每条消息由类型编号来分类,这样接收进程可以选择读取特定的消息类型——这一点与管道不同。消息队列在创建后将一直存在,直到使用msgctl系统调用或iqcrm -q命令删除它为止。
---- 系统提供了许多有关创建、使用和管理消息队列的系统调用,如:
---- int msgget(key,flag)——创建一个具有flag权限的MQ及其相应的结构,并返回一个唯一的正整数msqid(MQ的标识符);
---- int msgsnd(msqid,msgp,msgsz,msgtyp,flag)——向队列中发送信息;
---- int msgrcv(msqid,cmd,buf)——从队列中接收信息;
---- int msgctl(msqid,cmd,buf)——对MQ的控制操作;
(4) 共享存储段(SM)
---- 共享存储段是主存的一部分,它由一个或多个独立的进程共享。各进程的数据段与共享存储段相关联,对每个进程来说,共享存储段有不同的虚拟地址。系统提供的有关SM的系统调用有:
---- int shmget(key,size,flag)——创建大小为size的SM段,其相应的数据结构名为key,并返回共享内存区的标识符shmid;
---- char shmat(shmid,address,flag)——将当前进程数据段的地址赋给shmget所返回的名为shmid的SM段;
---- int shmdr(address)——从进程地址空间删除SM段;
---- int shmctl (shmid,cmd,buf)——对SM的控制操作;
---- SM的大小只受主存限制,SM段的访问及进程间的信息交换可以通过同步读写来完成。同步通常由信号灯来实现。SM非常适合进程之间大量数据的共享。
(5) 信号灯
---- 在UNIX中,信号灯是一组进程共享的数据结构,当几个进程竞争同一资源时(文件、共享内存或消息队列等),它们的操作便由信号灯来同步,以防止互相干扰。
---- 信号灯保证了某一时刻只有一个进程访问某一临界资源,所有请求该资源的其它进程都将被挂起,一旦该资源得到释放,系统才允许其它进程访问该资源。信号灯通常配对使用,以便实现资源的加锁和解锁。
进程间通信的实现技术的特点是:操作系统提供实现机制和编程接口,由用户在程序中实现,保证进程间可以进行快速的信息交换和大量数据的共享。
五、线程
1、线程优点:
所有线程可以直接共享内存和变量等; (与多进程相比,缺点是线程同步需要锁,线程出问题可能影响整个程序)
有的问题通过分解任务从而改变整个程序的吞吐量。(与单进程相比)
注意:linux中,进程和线程实现方式一样(只不过线程和其父进程共享了一部分数据),所以线程和进程的性能,上下文切换的消耗是差不多的。
2、多进程的优点:
不需要线程同步的锁;
各进程独立,不互相影响。
3、线程同步:
多个线程共享相同内存时,需要确保每个线程看到一致的数据。linux基本的同步机制:
一、互斥量(mutex)
互斥量本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。
对互斥量进行加锁以后,任何其它试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其它线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。在这种情况下,每次只有一个线程可以向前执行。
二、信号量(semaphore)
互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
三、读写锁
读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。
读写锁可以由三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
在读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。虽然读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。
读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为当前只有一个线程可以在写模式下拥有这个锁。当读写锁在读状态下时,只要线程获取了读模式下的读写锁,该锁所保护的数据结构可以被多个获得读模式锁的线程读取。
另外,读写锁是一种自旋锁,自旋锁比较适用于锁使用者保持锁时间比较短的情况。因为如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里而不是投入睡眠,直到该自旋锁的保持者释放了锁。自旋锁的效率远高于互斥锁,但是会占用比较多的cpu资源。
读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当他以写模式锁住时,它是以独占模式锁住的。
四、条件变量(condition)和监视器(monitor)
条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其它线程在获得互斥量之前不会察觉到这种改变,因此必须锁定互斥量以后才能计算条件。
这是《UNIX环境高级编程》中介绍的典型存储器安排,但现实也不一定非要如此。对于c/c++来说,数据的存储方式还是认为3种:堆、栈、全局数据区(包括全局数据、静态数据、常量)。
二、僵尸进程和孤儿进程
进程在终止前向父进程发送SIGCLD信号,父进程调用wait等待子进程的退出!
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程(ps进程状态为“Z”)。
孤儿进程没有什么危害,因为将来结束时,init进程会完成他们状态收集的工作。而僵尸进程在内核中还有一些信息(包括进程号),需要及时处理;但是每个进程都要或长或短地经历僵尸进程的状态。
三、fork和vfork
调用fork一次返回2次,分别在父进程和子进程中返回,父进程中其返回值是子进程的进程标识符,子进程中其返回值是0。
fork创建一个进程时,子进程复制父进程的数据段,堆栈,共享父进程的代码段。但是为了降低开销,现在Linux中是采取了copy-on-write(COW写时复制)技术,只有在写入的时候,才复制写入的那块内存,一般为虚拟内存的一“页”(即部分复制)。
vfork,1、保证子进程先运行,即在调用exec和exit之前父进程不运行,也就是如果子进程依赖父进程的进一步动作,将陷入死锁。2、在调用exec和exit之前,子进程和父进程是共用数据段和代码段的,也就是子进程的数据更改会影响父进程。
四、进程间的通信
管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。
(1) 信号
---- 信号机制是UNIX为进程中断处理而设置的。它只是一组预定义的值,因此不能用于信息交换,仅用于进程中断控制。例如在发生浮点错、非法内存访问、执行无 效指令、某些按键(如ctrl-c、del等)等都会产生一个信号,操作系统就会调用有关的系统调用或用户定义的处理过程来处理。
---- 信号处理的系统调用是signal,调用形式是:
---- signal(signalno,action)
---- 其中,signalno是规定信号编号的值,action指明当特定的信号发生时所执行的动作。
(2) 无名管道和有名管道
---- 无名管道实际上是内存中的一个临时存储区,它由系统安全控制,并且独立于创建它的进程的内存区。管道对数据采用先进先出方式管理,并严格按顺序操作,例如不能对管道进行搜索,管道中的信息只能读一次。
---- 无名管道只能用于两个相互协作的进程之间的通信,并且访问无名管道的进程必须有共同的祖先。
---- 系统提供了许多标准管道库函数,如:
pipe()——打开一个可以读写的管道;
close()——关闭相应的管道;
read()——从管道中读取字符;
write()——向管道中写入字符;
---- 有名管道的操作和无名管道类似,不同的地方在于使用有名管道的进程不需要具有共同的祖先,其它进程,只要知道该管道的名字,就可以访问它。管道非常适合进程之间快速交换信息。
(3) 消息队列(MQ)
---- 消息队列是内存中独立于生成它的进程的一段存储区,一旦创建消息队列,任何进程,只要具有正确的的访问权限,都可以访问消息队列,消息队列非常适合于在进程间交换短信息。
---- 消息队列的每条消息由类型编号来分类,这样接收进程可以选择读取特定的消息类型——这一点与管道不同。消息队列在创建后将一直存在,直到使用msgctl系统调用或iqcrm -q命令删除它为止。
---- 系统提供了许多有关创建、使用和管理消息队列的系统调用,如:
---- int msgget(key,flag)——创建一个具有flag权限的MQ及其相应的结构,并返回一个唯一的正整数msqid(MQ的标识符);
---- int msgsnd(msqid,msgp,msgsz,msgtyp,flag)——向队列中发送信息;
---- int msgrcv(msqid,cmd,buf)——从队列中接收信息;
---- int msgctl(msqid,cmd,buf)——对MQ的控制操作;
(4) 共享存储段(SM)
---- 共享存储段是主存的一部分,它由一个或多个独立的进程共享。各进程的数据段与共享存储段相关联,对每个进程来说,共享存储段有不同的虚拟地址。系统提供的有关SM的系统调用有:
---- int shmget(key,size,flag)——创建大小为size的SM段,其相应的数据结构名为key,并返回共享内存区的标识符shmid;
---- char shmat(shmid,address,flag)——将当前进程数据段的地址赋给shmget所返回的名为shmid的SM段;
---- int shmdr(address)——从进程地址空间删除SM段;
---- int shmctl (shmid,cmd,buf)——对SM的控制操作;
---- SM的大小只受主存限制,SM段的访问及进程间的信息交换可以通过同步读写来完成。同步通常由信号灯来实现。SM非常适合进程之间大量数据的共享。
(5) 信号灯
---- 在UNIX中,信号灯是一组进程共享的数据结构,当几个进程竞争同一资源时(文件、共享内存或消息队列等),它们的操作便由信号灯来同步,以防止互相干扰。
---- 信号灯保证了某一时刻只有一个进程访问某一临界资源,所有请求该资源的其它进程都将被挂起,一旦该资源得到释放,系统才允许其它进程访问该资源。信号灯通常配对使用,以便实现资源的加锁和解锁。
进程间通信的实现技术的特点是:操作系统提供实现机制和编程接口,由用户在程序中实现,保证进程间可以进行快速的信息交换和大量数据的共享。
五、线程
1、线程优点:
所有线程可以直接共享内存和变量等; (与多进程相比,缺点是线程同步需要锁,线程出问题可能影响整个程序)
有的问题通过分解任务从而改变整个程序的吞吐量。(与单进程相比)
注意:linux中,进程和线程实现方式一样(只不过线程和其父进程共享了一部分数据),所以线程和进程的性能,上下文切换的消耗是差不多的。
2、多进程的优点:
不需要线程同步的锁;
各进程独立,不互相影响。
3、线程同步:
多个线程共享相同内存时,需要确保每个线程看到一致的数据。linux基本的同步机制:
一、互斥量(mutex)
互斥量本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。
对互斥量进行加锁以后,任何其它试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其它线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。在这种情况下,每次只有一个线程可以向前执行。
二、信号量(semaphore)
互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
三、读写锁
读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。
读写锁可以由三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
在读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。虽然读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。
读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为当前只有一个线程可以在写模式下拥有这个锁。当读写锁在读状态下时,只要线程获取了读模式下的读写锁,该锁所保护的数据结构可以被多个获得读模式锁的线程读取。
另外,读写锁是一种自旋锁,自旋锁比较适用于锁使用者保持锁时间比较短的情况。因为如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里而不是投入睡眠,直到该自旋锁的保持者释放了锁。自旋锁的效率远高于互斥锁,但是会占用比较多的cpu资源。
读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当他以写模式锁住时,它是以独占模式锁住的。
四、条件变量(condition)和监视器(monitor)
条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其它线程在获得互斥量之前不会察觉到这种改变,因此必须锁定互斥量以后才能计算条件。
相关文章推荐
- 好站
- 数字运算
- Android-屏幕适配全攻略(绝对详细)(二)
- 冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序java实现
- UNIX网络编程之旅-配置unp.h头文件环境
- 打印整数划分
- 前端代码重构
- 第三周学习进度条
- Description Resource Path Location Type The project was not built since its
- Codeforce 327D 二分
- android中内存泄漏和内存溢出的区别
- 继承和多态------虚析构函数(二)
- Recommended Django Project Layout
- POJ 1182 食物链 并查集
- 当应用程序不是以UserInteractive 模式运行时显示模式对话框或窗体
- 【网页设计】行业文字规范你知道吗?太原UI设计师讲
- POJ 1182 食物链 并查集
- mysql 5.1.x 比较 5.6.x版本的日志
- Struts1、Struts2、SpringMVC
- Newtonsoft.Json(Json.Net)学习笔记