虚拟存储器为每个进程提供了独占系统地址空间的假象。尽管每个进程地址空间内容不尽相同,但是他们的都有相似的结构。X86 Linux进程的地址空间底部是保留给用户程序的,包括文本、数据、堆、栈等,其中文本区和数据区是通过存储器映射方式将磁盘中可执行文件的相应段映射至虚拟存储器地址空间中。有一些"敏感"的地址需要注意下,对于32位进程来说,代码段从0x08048000开始。从0xC0000000开始到0xFFFFFFFF是内核地址空间,通常情况下代码运行在用户态(使用0x00000000 ~ 0xC00000000的用户地址空间),当发生系统调用、进程切换等操作时CPU控制寄存器设置模式位,进入内和模式,在该状态(超级用户模式)下进程可以访问全部存储器位置和执行全部指令。也就说32位进程的地址空间都是4G,但用户态下只能访问低3G的地址空间,若要访问3G ~ 4G的地址空间则只有进入内核态才行。
进程控制块(处理机)
进程的调度实际就是内核选择相应的进程控制块,被选择的进程控制块中包含了一个进程基本的信息。
上下文切换
内核管理所有进程控制块,而进程控制块记录了进程全部状态信息。每一次进程调度就是一次上下文切换,所谓的上下文本质上就是当前运行状态,主要包括通用寄存器、浮点寄存器、状态寄存器、程序计数器、用户栈和内核数据结构(页表、进程表、文件表)等。进程执行时刻,内核可以决定抢占当前进程并开始新的进程,这个过程由内核调度器完成,当调度器选择了某个进程时称为该进程被调度,该过程通过上下文切换来改变当前状态。一次完整的上下文切换通常是进程原先运行于用户态,之后因系统调用或时间片到切换到内核态执行内核指令,完成上下文切换后回到用户态,此时已经切换到进程B。
线程、进程比较
关于进程和线程的区别这里就不一一罗列了,主要对比下线程和进程操作中主要的接口。
fork()和pthread_create()
负责创建。调用fork()后返回两次,一次标识主进程一次标识子进程;调用pthread_create()后得到一个可以独立执行的线程。
wait()和pthread_join()
负责回收。调用wait()后父进程阻塞;调用pthread_join()后主线程阻塞。
exit()和pthread_exit()
负责退出。调用exit()后调用进程退出,控制权交给系统;调用pthread_exit()后线程退出,控制权交给主线程。
进程间通信Linux几乎支持全部UNIX进程间通信方法,包括管道(有名管道和无名管道)、消息队列、共享内存、信号量和套接字。其中前四个属于同一台机器下进程间的通信,套接字则是用于网络通信。
管道
无名管道
无名管道特点:
无名管道是一种特殊的文件,这种文件只存在于内存中。
无名管道只能用于父子进程或兄弟进程之间,必须用于具有亲缘关系的进程间的通信。
无名管道只能由一端向另一端发送数据,是半双工方式,如果双方需要同时收发数据需要两个管道。
相关接口:
int pipe(int fd[2]);
fd[2]:管道两端用fd[0]和fd[1]来描述,读的一端用fd[0]表示,写的一端用fd[1]表示。通信双方的进程中写数据的一方需要把fd[0]先close掉,读的一方需要先把fd[1]给close掉。
有名管道:
有名管道特点:
有名管道是FIFO文件,存在于文件系统中,可以通过文件路径名来指出。
无名管道可以在不具有亲缘关系的进程间进行通信。
相关接口:
int mkfifo(const char *pathname, mode_t mode);
pathname:即将创建的FIFO文件路径,如果文件存在需要先删除。
mode:和open()中的参数相同。
消息队列
共享内存