嵌入式-操作系统(一)
一.什么是进程?什么是线程?
进程是资源分配的基本单位,是程序执行时的实例,在程序运行时创建。
线程程序执行,即CPU调度和分配的最小单位,是进程的一个执行流,一个进程由多个线程组成。
二.进程与线程的区别
- 进程是资源分配的最小单位。
- 线程是程序执行的最小单位,也是处理器调度的最小单位。
- 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段以及数据段。而线程是共享进程中的数据,使用相同的地址空间,创建线程以及切换线程所花费的开销比进程小。
- 线程之间通信更加方便,同一个进程下的线程之间共享全局变量、静态变量等数据,而进程之间要以通信(IPC)的方式进行。但多进程程序会更加健壮,在多线程程序中,一个线程死掉就会影响整个进程死掉;而一个进程死掉并不会对另一个进程造成影响,因为进程有自己独立的地址空间。
- 进程切换时,消耗的资源大,效率低。所以涉及到频繁的切换时,使用线程要好于进程。如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
- 每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立运行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
三.什么时候适合使用多线程,什么时候适合适应多进程?
要求效率高,频繁切换时,使用多线程
四.进程同步与进程互斥
- 进程同步
进程同步是进程间的直接制约关系,是为了完成某种任务而建立的两个或多个进程,这个进程需要在某些位置上协调他们的工作次序而等待传递信息所产生的制约关系,进程间的直接制约关系来源于他们之间的合作。 - 进程互斥
进程互斥是进程间的间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,只有当使用临界资源的进程退出临界区后,这个进程才会解除阻塞状态。
五.进程调度中的“抢占式”与“非抢占式”
- 非抢占式
让进程运行到结束或阻塞的调度方式 - 抢占式
允许让逻辑上可继续运行的运行过程暂停的调度方式。可防止单一进程长时间占用CPU。
六.进程的几种状态
- 创建状态
应用程序从系统上启动,首先就是进入创建状态,需要系统资源创建程序控制块(PCB),完成资源分配。 - 就绪状态
除了CPU时间片外的其他资源都已获取的状态。 - 运行状态
获取处理器资源,被系统调度,开始进入运行状态。当CPU时间片使用完后,进入就绪状态。 - 阻塞状态
在进程运行状态,如果执行具有阻塞效果的函数,此时进程暂时无法操作就进入到了阻塞状态,当这些操作执行结束之后,就可进入就绪状态。 - 终止状态
进程结束或系统中止,即可进入终止状态。
七.进程间通信(IPC)
1.管道(pipe)
管道是一种只能在具有亲缘关系的进程间使用的半双工通信方式。其中的亲缘关系进程通常指的是父子进程;半双工指的是数据只能单向流动。
(1)管道的特点:
- 管道具有文件的性质:读操作、写操作
- 在管道中数据的传递方向是单向的,一端用于写入、一端用于读出
- 在管道中读取数据是一次性的的操作,数据一旦从管道中读走,就会被管道所抛弃,释放出内存以便写入更多的数据。
(2)管道读写的特点:
- 当所有指向管道写端(pipefd[1])的文件描述符都关闭时(即管道写端引用计数为0),此时若有进程从管道的读端(pipefd[0])读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。
- 当存在指向管道写端(pipefd[1])的文件描述符没有关闭时(管道的写端引用计数大于0),而持有管道写端的进程也没有往管道中写数据,此时若有进程从管道中读取数据,那么管道中剩余的数据被读取后,再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。
- 当所有指向管道读端(pipefd[0])的文件描述符都关闭时(管道的读端引用计数为0),这个时候有进程向管道中写数据,那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。
- 当存在指向管道读端的文件描述符没有关闭时(管道的读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道中写数据,那么在管道被写满的时候再次write会阻塞,直到管道中有空位置才能再次写入数据并返回。
//创建匿名管道
#include <unistd.h>
int pipe(int pipefd[2]);
2.有名管道(FIFO)
有名管道(FIFO)不同于匿名管道之处在于它提供了一个路径名与之关联,以 FIFO的文件形式存在于文件系统中(即存在实体),并且其打开方式与打开一个普通文件是一样的,这样即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。
有名管道(FIFO)和匿名管道(pipe)有一些特点是相同的,不一样的地方在于:
- FIFO 在文件系统中作为一个特殊文件存在,但 FIFO 中的内容却存放在内存中。
- 当使用 FIFO 的进程退出后, FIFO 文件将继续保存在文件系统中以便以后使用。
- FIFO 有名字,不相关的进程可以通过打开有名管道进行通信
//创建有名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
3.信号量(semophore)
信号量也可以认为是一种计数器,可以用开控制多个进程对共享资源的访问。信号量通常防止某进程在访问共享资源时,其他进程也访问该资源。因此,信号量作为进程间或同一进程中多线程之间的同步手段。
优缺点:不能用来传递复杂信息,只能用来同步
4.信号(singnal)
信号用于通知接收某个事件已经发生。主要作为进程间或同一进程中多线程之间的同不手段。
5.消息队列(message queue)
消息队列是由消息组成的链表,存放在内核中并由消息队列标识符标识。
6.共享内存(shared memory)
共享内存就是映射一段能够被其他进程所访问的内存。这段共享内存由一个进程创建,但能够被多个进程所访问,共享内存是最快的一种进程间通信方式,通常与信号量等其他通信方式配合使用,实现进程间的通信与同步。
//创建一个新的共享内存段,或者获取一个既有的共享内存段的标识
int shmget(key_t key, size_t size, int shmflg);
调用 shmget() 创建一个新共享内存段或取得一个既有共享内存段的标识符(即由其他进程创建的共享内存段,新创建的内存段中的数据都会被初始化为0)。这个调用将返回后续调用中需要用到的共享内存标识符。
//和当前的进程进行关联
void *shmat(int shmid, const void *shmaddr, int shmflg);
使用 shmat() 来附上共享内存段,即使该段成为调用进程的虚拟内存的一部分。此刻在程序中可以像对待其他可用内存那样对待这个共享内存段。为引用这块共享内存,程序需要使用由 shmat() 调用返回的 addr 值,它是一个指向进程的虚拟地址空间中该共享内存段的起点的指针。
//解除当前进程和共享内存的关联
int shmdt(const void *shmaddr);
调用 shmdt() 来分离共享内存段。在这个调用之后,进程就无法再引用这块共享内存了。这一步是可选的,并且在进程终止时会自动完成这一步。
//对共享内存进行操作
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
调用 shmctl() 来删除共享内存段。只有当当前所有附加内存段的进程都与之分离之后内存段才会销毁。只有一个进程需要执行这一步。
7.套接字(socket)
用于不同机器间的进程通信
八.线程同步与线程互斥
- 线程同步
在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问,线程间知道彼此的存在。 - 线程互斥
某一资源同时只允许一个访问者对其进行访问,但互斥无法限制访问者对资源的访问顺序,即访问是无序的,线程间不需要知道彼此的存在。
九.线程间的同步方式
1.临界区
通过对多线程的串行化来访问公共资源或一串代码。在任意时刻只允许一个线程访问共享资源,如果有段个线程试图访问,当一个线程已经进入后,其他试图访问共享资源的线程将会被挂起,并一直等到进入临界区的线程离开,其他线程才可以抢占。
2.互斥量
因为互斥量只有一个,所以只有拥有互斥量的线程,才有权限去访问系统的公共资源,能够保证资源不会同时被多个线程访问。并且互斥不仅能够实现同一应用程序的公共资源安全共享,还能实现不同应用程序的资源共享。
3.信号量
信号量允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻的最大访问线程数。
4.事件
用来通知线程有一些事件已经发生,从而开始继续执行后续任务。