进程###
进程基本知识:
进程结构、进程调度、进程状态
http://www.jellythink.com/archives/900
- 进程的结构
子进程复制了父进程的什么
http://www.cnblogs.com/zhangchaoyang/articles/2317420.html
fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了读时共享,写时复制技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。
当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。
fork之后内核会通过将子进程放在队列的前面,以让子进程先执行,以免父进程执行导致写时复制,而后子进程执行exec系统调用,因无意义的复制而造成效率的下降。
fork时子进程获得父进程数据空间、堆和栈的复制,所以变量的地址(当然是虚拟地址)也是一样的。
每个进程都有自己的虚拟地址空间,不同进程的相同的虚拟地址显然可以对应不同的物理地址。因此地址相同(虚拟地址)而值不同没什么奇怪。
具体过程是这样的:fork子进程完全复制父进程的栈空间,也复制了页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”(类似mmap的private的方式),如果父子进程一直对这个页面是同一个页面,知道其中任何一个进程要对共享的页面“写操作”,这时内核会复制一个物理页面给这个进程使用,同时修改页表。而把原来的只读页面标记为“可写”,留给另外一个进程使用。这就是所谓的“写时复制”。
正因为fork采用了这种写时复制的机制,所以fork出来子进程之后,父子进程哪个先调度呢?内核一般会先调度子进程,因为很多情况下子进程是要马上执行exec,会清空栈、堆。。这些和父进程共享的空间,加载新的代码段。。。,这就避免了“写时复制”拷贝共享页面的机会。如果父进程先调度很可能写共享页面,会产生“写时复制”的无用功。所以,一般是子进程先调度滴。进程调度
在单个处理器上,同一时间只能有一个进程可以运行,其它进程都处于等待运行状态。但是,我们实际的感觉是同一时刻有多个进程在“同时”进行运行,这是为什么呢?
操作系统会分给每个进程一定的运行时间,叫做“时间片”。进程在这个“时间片”内运行,由于“时间片”非常的短,这样就给人一种多个程序在同时运行的假象。操作系统是如何调度进程的呢?进程调度的规则有很多,比如根据优先级进行调度算法,先进先出调度算法等。
Linux内核进程调度器是根据进程的优先级来进行进程调度的。优先级高的进程运行得更为频繁。
进程间通信###
参考文章####
- 各种进程间通信方法都讲了
讲的比较好的,缺socket
http://read.pudn.com/downloads113/ebook/470525/linux%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1.pdf
总结了一波,各种进程间通信方法优缺点
https://www.ibm.com/developerworks/cn/linux/l-ipc/
讲的可以参考
https://segmentfault.com/a/1190000004629793
各种进程间通信方法实践
https://github.com/clpsz/linux-ipcs
管道####
参考文章
讲环形缓冲区很细致
http://blog.csdn.net/yangwen123/article/details/14118733
四种情况讲的很细致
http://zxtong.blog.51cto.com/10697148/1763104
管道和fifo
http://blog.csdn.net/anonymalias/article/details/9391743管道的局限性(匿名管道)
管道的主要局限性正体现在它的特点上:只支持单向数据流;(pipe单向通信,因为父子进程共享同一个file结构体)
只能用于具有亲缘关系的进程之间;
没有名字;
管道的缓冲区是有限的(管道只存在于内核空间中,在管道创建时,为缓冲区分配一个页面大小);
管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等
FIFO(First In First Out),又称为有名管道,弥补了管道只能用于有共同祖先的进程间通信的不足,相对于pipe,fifo主要有以下几个优点:
可以用于任意进程之间通信
可以有多个读/写进程同时对管道进行操作
可以像变通文件一样管理管道的权限
可以使用标准文件读写方式来操作管道(打开,读/写,关闭)
信号量####
。。。。。
unix domain socket####
这个文章里有例子
http://www.jianshu.com/p/431aeca69b07
进程的同步与互斥###
这篇文章讲的很好:
http://www.cnblogs.com/CareySon/archive/2012/04/14/Process-SynAndmutex.html
上文总结出的同步和互斥:
-
临界资源
在操作系统中,进程是占有资源的最小单位(线程可以访问其所在进程内的所有资源,但线程本身并不占有资源或仅仅占有一点必须资源)。但对于某些资源来说,其在同一时间只能被一个进程所占用。这些一次只能被一个进程所占用的资源就是所谓的临界资源。典型的临界资源比如物理上的打印机,或是存在硬盘或内存中被多个进程所共享的一些变量和数据等(如果这类资源不被看成临界资源加以保护,那么很有可能造成丢数据的问题)。对于临界资源的访问,必须是互诉进行。也就是当临界资源被占用时,另一个申请临界资源的进程会被阻塞,直到其所申请的临界资源被释放。而进程内访问临界资源的代码被成为临界区。
对于临界区的访问过程分为四个部分:
1.进入区:查看临界区是否可访问,如果可以访问,则转到步骤二,否则进程会被阻塞
2.临界区:在临界区做操作
3.退出区:清除临界区被占用的标志
4.剩余区:进程与临界区不相关部分的代码
-
进程同步
进程同步也是进程之间直接的制约关系,是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系来源于他们之间的合作。
比如说进程A需要从缓冲区读取进程B产生的信息,当缓冲区为空时,进程B因为读取不到信息而被阻塞。而当进程A产生信息放入缓冲区时,进程B才会被唤醒。
-
进程互斥
进程互斥是进程之间的间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待。只有当使用临界资源的进程退出临界区后,这个进程才会解除阻塞状态。
比如进程B需要访问打印机,但此时进程A占有了打印机,进程B会被阻塞,直到进程A释放了打印机资源,进程B才可以继续执行。
-
实现临界区互斥的基本方法
硬件实现方法(以前不知道)
通过硬件实现临界区最简单的办法就是关CPU的中断。从计算机原理我们知道,CPU进行进程切换是需要通过中断来进行。如果屏蔽了中断那么就可以保证当前进程顺利的将临界区代码执行完,从而实现了互斥。这个办法的步骤就是:屏蔽中断--执行临界区--开中断。但这样做并不好,这大大限制了处理器交替执行任务的能力。并且将关中断的权限交给用户代码,那么如果用户代码屏蔽了中断后不再开,那系统岂不是跪了?
还有硬件的指令实现方式,这个方式和接下来要说的信号量方式如出一辙。但是通过硬件来实现,这里就不细说了。
信号量实现方式
这也是我们比较熟悉P V操作。通过设置一个表示资源个数的信号量S,通过对信号量S的P和V操作来实现进程的的互斥。
P和V操作分别来自荷兰语Passeren和Vrijgeven,分别表示占有和释放。P V操作是操作系统的原语,意味着具有原子性。P操作首先减少信号量,表示有一个进程将占用或等待资源,然后检测S是否小于0,如果小于0则阻塞,如果大于0则占有资源进行执行。
V操作是和P操作相反的操作,首先增加信号量,表示占用或等待资源的进程减少了1个。然后检测S是否小于0,如果小于0则唤醒等待使用S资源的其它进程。
linux线程同步和进程同步的区别###
http://blog.csdn.net/daiyudong2020/article/details/51707823
和进程相比,使用线程的理由###
进程和线程
进程是程序执行时的一个实例,即它是程序已经执行到何种程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。
线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。
"进程——资源分配的最小单位,线程——程序执行的最小单位"
进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
使用线程的理由
- 使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。
我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。 - 使用多线程的理由之二是线程间方便的通信机制。
对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。 - 除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
- 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
- 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
- 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
线程同步###
线程同步,同步的是什么?它同步的是对共享资源(内存区域,公共变量等)或者临界区域的访问。有的时候,这些共享资源和临界区域,就只能容忍一个线程对它进行操作(读或者写,读操作一般不控制,主要是写操作),这个时候,我们必须要对这些共享资源或者临界区域进行同步,那么如何对它们进行线程同步呢?
-
进程和线程之间的关系
-
线程共享的资源
-
线程之间非共享的资源
-
线程为什么要同步
1.共享资源,多个线程都可对共享资源操作
2.线程操作共享资源的先后顺序不确定
3.处理器对存储器的操作一般不是原子操作(如加法分了三步) - 线程同步方法:
-
互斥量
http://www.jellythink.com/archives/818 -
读写锁
- 读共享,写独占
- 通常用在读的次数远远多于写的次数的情况
- 用互斥量也可以实现,但是性能不好,读的时候也会加锁、阻塞等待拿锁,那么等待的时间明显长于用读写锁
- 如果要请求写锁,必须等读锁都释放了才会请求到
即读写锁不是各加各的。加读锁,也要等写锁释放了
- 条件变量
如果说互斥锁是用于同步线程对共享数据的访问的话,那么条件变量则是用于在线程之间同步共享数据的值。
条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值得时候,唤醒等待这个共享数据的线程。
和互斥锁配合一起使用,适用于典型的生产者消费者模型。
一个线程把必须先干一件事情,然后才能干接下来的事情:发信号给等待队列上的所有线程-
信号量(semaphore)
http://www.jellythink.com/archives/814- 可以用于线程,也可用于进程
互斥锁的升级版 - 互斥锁只有0/1两种状态,即只有一把锁可以用
信号量的话0/n,可以有n把锁可用
- 可以用于线程,也可用于进程
进程间通信###
同线程间同步的方法,如下:
- 互斥量
- 读写锁
- 条件变量
- 信号量(semaphore)
此外还有:
- 记录锁(record locking)
- 记录锁机制的功能是:一个进程正在读或修改文件的某个部分时,可以阻止其它进程修改同一文件区。对于Unix,记录这个定语也是误用,因为Unix系 统核根本没有使用文件记录这种概念。一个更适合的术语可能是区域锁,因为它锁定的只是文件的一个区域(也可能是整个文件)。
Linux内核没有文件内的记录这一概念。任何关于记录的接收都是由读写文件的应用来进行的。然而linux内核提供的上锁特性却用记录上锁(record locking)这一术语描述。不过应用会指定文件中待上锁或解锁部分的字节范围,因为记录锁锁定的只是文件中的一个区域。
记录锁是读写锁的一种拓展,可用于亲缘或非亲缘关系的进程间共享某个文件的读写。被锁住的文件通过其描述符访问,执行上锁操作的函数是fcntl。这类锁通常维护在内核中,其属主是由属主的进程ID来标识的。这意味着这些锁用于不同进程间的上锁,不适用于同一进程不同线程间上锁。