导图社区 进程
这是一篇关于进程的思维导图,主要内容包括:进程上下文,状态,进程调度,作业,特征要素,进程同步,概念,分类,相关接口,IPC,进程数据结构,调试。
编辑于2025-02-28 11:04:59这是一篇关于程序和库信息的思维导图,主要内容包括:(查看基础信息),获取ELF节的长度信息,显示可执行文件或库需要静态加载的动态库完整列表--显示加载时的依赖项,列出二进制文件的节信息,查看动态节,列出并查看段,查看重定位节,反汇编,列出库中未定义的符号,列出动态符号,列出二进制文件或库的符号表,查看节中的数据,符号的类型。
这是一篇关于设备驱动的思维导图,主要内容包括:主要功能,设备驱动模型。阐述了设备驱动的主要功能、信号定义、设备驱动模型等内容。
这是一篇关于算法的思维导图,主要内容包括:云计算,内存管理算法,分布式同步算法,避免死锁算法,进程调度算法,磁盘调度算法。
社区模板帮助中心,点此进入>>
这是一篇关于程序和库信息的思维导图,主要内容包括:(查看基础信息),获取ELF节的长度信息,显示可执行文件或库需要静态加载的动态库完整列表--显示加载时的依赖项,列出二进制文件的节信息,查看动态节,列出并查看段,查看重定位节,反汇编,列出库中未定义的符号,列出动态符号,列出二进制文件或库的符号表,查看节中的数据,符号的类型。
这是一篇关于设备驱动的思维导图,主要内容包括:主要功能,设备驱动模型。阐述了设备驱动的主要功能、信号定义、设备驱动模型等内容。
这是一篇关于算法的思维导图,主要内容包括:云计算,内存管理算法,分布式同步算法,避免死锁算法,进程调度算法,磁盘调度算法。
进程
进程上下文
用户级上下文
正文代码段
数据
用户栈
共享存储区
寄存器上下文
程序计数器PC
处理机状态寄存器
栈指针
通用寄存器
系统级上下文
发生中断时 或一个进程发生系统调用时 或进程进行上下文切换时 内核就压入一个系统级上下文 当内核从中断处理返回 或进程完成系统调用后返回用户态 或一个进程进行上下文切换时 内核弹出一个系统级上下文 内核压入老的进程的上下文层 弹出新的进程的上下文层 进程表表项存放着当前上下文层所必须的信息
进程表表项
定义了进程的状态 内核能取到的控制信息
进程的u区
进程的控制信息 这些信息在该进程的山下文中被存取 一般的控制参数(如进程优先级)被放在进程表中 它们在进程的外部被存取
本进程区表表项 区表和页表
定义从虚拟地址到物理地址的映射 因而决定了进程的正文区 数据区 栈区和其他区
核心栈
含有内核过程的栈结构 每个进程各自有一份核心栈的拷贝指出了它们各自对内核函数的特殊调用 栈中装有它们各自的函数调用序列 内核必须能恢复核心栈的内容及核心栈指针的位置 从而使一个进程能重新在核心态下运行
状态
运行态 R (TASK_RUNNING):此时进程正在使用 CPU 资源 暂停态 T (TASK_STOPPED or TASK_TRACED):向进程发送一个 SIGSTOP 信号, 它就会因响应该信号而进入 TASK_STOPPED 状态 向进程发送一个 SIGCONT 信号,可以让其从 TASK_STOPPED 状态恢复到 TASK_RUNNING 状态, 当进程正在被跟踪时,它处于 TASK_TRACED 这个特殊的状态。 “正在被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作 可中断睡眠态 S (TASK_INTERRUPTIBLE):处于这个状态的进程因为等待某些事件的发生而进入睡眠状态 不可中断睡眠状态 D (TASK_UNINTERRUPTIBLE):不可中断睡眠状态又被称作深度睡眠态, 是把信号传递到这种睡眠状态的进程不能改变它的状态,也就是说它不响应信号的唤醒, 这种状态一般由 IO 引起,同步 IO 在做读或写操作时 (比如进程对某些硬件设备进行操作,等待磁盘 IO,等待网络 IO) 此时 CPU 不能做其它事情,只能处于这种状态进行等待,这样一来就能保证进程执行期间不被外部信号打断 僵死态 Z (TASK_DEAD - EXIT_ZOMBIE):僵死态又被称之为僵尸态或者退出态, 进程在退出的过程中,除了 task_struct 数据结构(以及少数资源)以外, 进程所占有的资源将被系统回收,此时进程没法继续运行了, 但它还有 task_struct 数据结构,所以被称为僵死态 之所以保留 task_struct 数据结构,是因为 task_struct 中保存了进程的退出码、以及一些其他的信息, 而其父进程很可能会关心这些信息,因此会暂时被保留下来
内核对应的宏
状态转换
引起进程阻塞的事件
向系统请求共享资源失败
等待某种操作完成
数据尚未到达
等待新任务到来
ps命令状态说明
孤儿
孤儿进程组
一个进程组到其组外的父进程之间的联系依赖于该父进程和其子进程2者
组外最后一个链接父进程的进程终止
最后一个父进程的之间后裔终止
死锁
产生条件
互斥
任一时刻只允许一个进程使用资源
请求和保持
进程在请求其余资源时 不主动释放已经占用的资源
非剥夺
进程已经占用的资源 不会被强制剥夺
环路等待
环路中的每一条边时进程在请求另一进程已经占用的资源
处理方法
进程调度
调度条件
进程被另外一个具有更高实时优先级的实时进程抢占
进程执行了阻塞操作并进入睡眠
进程停止或被杀死
进程通过sched_yield自愿放弃CPU
进程是基于时间片轮转的实时进程(SCHED_RR)且用完了它的时间片
主要任务
1.保存CPU现场信息
2.根据算法选取进程
3.把CPU分配给进程
调度机制
排队器
为了提高进程调度的效率 事先将系统中的所有就绪进程按照一定策略排成一个或多个队列 以便调度程序能最快找到它们 以后每当有一个进程转变成就绪状态时 排队器就将它们插入相应的就绪队列
分派器
将进程调度程序所选定的进程从就绪队列取出 然后进行从分派器到新选进程间的上下文切换 以将CPU分配给新选进程
上下文切换器
在进行上下文切换时 需要执行大量的load和store指令 以保存寄存器的内容 一般采用2组寄存器 一组寄存器供处理机在内核态时使用 另一组寄存器供应用程序使用 这样上下文切换 只需改变指针以使其指向当前寄存器组即可
第一对上下文切换时 OS将保存当前进程的上下文 把当前进程的CPU寄存器内容保存到该进程PCB内相应单元 而装入分派程序的上下文 方便分派程序运行
第二对上下文切换是移出分派程序的上下文 把新选进程的CPU现场信息装入CPU的各相应寄存器组中 以便新选进程运行
调度方式
非抢占调度方式
抢占调度方式
抢占原则
优先级
短进程优先
时间片
作业
JBC
作业标志
用户名称
用户账户
作业类型
CPU繁忙型
IO繁忙型
批量型
终端型
作业状态
调度信息
优先级
作业运行情况
资源要求
入系统时间
作业完成时间
作业退出时间
资源使用情况
特征要素
专用的系统堆栈空间
进程控制块PCB
作用
作为独立运行基本单位的标志
实现间断性运行方式
提供进程管理所需要的信息
提供进程调度所需要的信息
实现与其他进程的同步与通信
组成要素
进程标识符
处理机状态
通用寄存器
指令计数器
程序状态字寄存器
用户栈指针寄存器
进程调度信息
进程状态
进程优先级
进程调度所需的时间信息
事件(阻塞原因) 指进程由执行状态转换为阻塞状态所等待发生的事件
进程控制信息
程序和数据的地址
进程同步和通信机制
资源清单
联机指针(下一个进程PCB的地址)
组织方式
线性方式
将系统中所有的PCB组织在一张线性表中 将该表的起始地址存放在内存的专用区域
链接方式
通过PCB的链接指针 将具有相同状态进程的PCB链接成一个队列 形成就绪队列 阻塞队列 空闲队列等
索引方式
系统根据所有进程的状态不同 建立几张索引表(就绪索引表 阻塞索引表等) 并把各索引表在内存中的起始地址记录在内存的一些专用单元中 每个索引表的表项 记录具有相应状态的某个PCB在PCB表中的地址
独立的用户空间
包括用于虚存管理的mm_struct以及其下属的vm_area 以及相应的页目录项和页表
不独立的用户空间=>线程
完全没有用户空间=>内核线程
资源
进程同步
准则
空闲让进
当无进程处于临界区时 表明临界资源处于空闲状态 应允许一个请求进入临界区的进程立即进入临界区 以有效的利用资源
忙则等待
当已有进程在临界区时 表明临界资源正在被访问 因而其他试图进入临界区的进程必须等待 以保证对临界资源的互斥访问
有限等待
对要求访问临界资源的进程 硬保证其能在有限时间内进入临界区 以避免陷入死等状态
让权等待
当进程不能进入临界区时 应立即释放处理机 以免陷入忙等
临界区
临界资源
机制
锁
信号量
硬件互斥方法
开关中断
Test-and-Set指令
Swap指令
概念
进程栈 进程运行时自身的堆栈
进程表 存储进程状态信息的数据结构
内核栈 进程调度模块运行时使用的堆栈
分类
分类方式1
交互式进程
命令shell
文本编辑程序
图形应用程序
批处理进程
程序设计语言的编译程序
数据库搜索引擎
科学计算
实时进程
音视频应用程序
机器人控制程序
传感器相关数据收集的程序
分类方式2
活动进程
这些进程还没有用完它们的时间片 因此允许它们运行
过期进程
这些可运行进程已经用完了它们的时间片 并因此被禁止运行直到所有活动进程都过期
相关接口
创建
内核在系统的物理内存中为新的进程分配新的 task_struct 结构 同时为新进程要使用的堆栈分配物理页 Linux 还会为新的进程分配新的进程标识符 然后,新 task_struct 结构的地址保存在链表中 而旧进程的task_struct 结构内容被复制到新进程的 task_struct 结构中
可共享的资源
文件
信号处理程序
虚拟内存
方法
clone
fork
kernel_thread
IPC
公共的数据结构 pc_perm 结构 struct ipc_perm { key_t key; /* 键 */ ushort uid; /* 对象拥有者对应进程的有效用户识别号和有效组识别号 */ ushort gid; ushort cuid; /* 对象创建者对应进程的有效用户识别号和有效组识别号 */ ushort cgid; ushort mode; /* 存取模式 */ ushort seq; /* 序列号 */ };
管道
所谓管道,是指用于连接一个读进程和一个写进程,以实现它们之间通信的共享文件,又称 pipe 文件 向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入管道 而接收管道输出的接收进程(即读进程),可从管道中接收数据
注意点
互斥
当一个进程正在对 pipe 进行读/写操作时,另一个进程必须等待
同步
当写(输入)进程把一定数量(如 4KB)数据写入 pipe 后,便去睡眠等待, 直到读(输出)进程取走数据后,再把它唤醒。 当读进程读到一空 pipe 时,也应睡眠等待, 直至写进程将数据写入管道后,才将它唤醒
只有确定对方已存在时,才能进行通信
管道的实现
管道读函数 pipe_read
通过复制物理内存中的字节而读出数据 进程可以在没有数据或内存被锁定时立即返回错误信息,而不是阻塞该进程 进程可以休眠在索引节点的等待队列中等待写入进程写入数据 当所有的进程完成了管道操作之后,管道的索引节点被丢弃,而共享数据页也被释放
管道写函数 pipe_wrtie
通过将字节复制到 VFS 索引节点指向的物理内存而写入数据 首先检查 VFS 索引节点中的信息(1.内存中有足够的空间可容纳所有要写入的数据 2 内存没有被读程序锁定) 写入函数首先锁定内存,然后从写进程的地址空间中复制数据到内存 否则(1|2条件不满足时) 写入进程就休眠在 VFS 索引节点的等待队列中 接下来,内核将调用调度程序,而调度程序会选择其他进程运行 写入进程实际处于可中断的等待状态,当内存中有足够的空间可以容纳写入数据,或内存被解锁时 读取进程会唤醒写入进程,这时,写入进程将接收到信号 当数据写入内存之后,内存被解锁,而所有休眠在索引节点的读取进程会被唤醒
使用实例
#include <stdio.h> #include <unistd.h> #include <sys/types.h> int main(void) { int fd[2], nbytes; pid_t childpid; char string[] = "Hello, world!\n"; char readbuffer[80]; pipe(fd); if((childpid = fork()) == -1) { printf("Error:fork"); exit(1); } if(childpid == 0) /* 子进程是管道的写进程 */ { close(fd[0]); /*关闭管道的读端 */ write(fd[1], string, strlen(string)); exit(0); } else /* 父进程是管道的读进程 */ { close(fd[1]); /*关闭管道的写端 */ nbytes = read(fd[0], readbuffer, sizeof(readbuffer)); printf("Received string: %s", readbuffer); } return(0); }
信号
设置信号响应
忽略信号
有两个信号不可以被忽略: SIGKILL,它将结束进程; SIGSTOP,它是作业控制机制的一部分,将挂起作业的执行
恢复信号的默认操作
登记特殊的信号处理函数
检测和响应信号时机
当前进程由于系统调用、中断或异常而进入内核空间以后,从内核空间返回到用户空间前夕 当前进程在内核中进入睡眠以后刚被唤醒的时候,由于检测到信号的存在而提前返回到用户空间
信号处理流程
(1)检查对应的 sigaction 结构,如果该信号不是 SIGKILL 或 SIGSTOP 信号,且被忽略,则不处理该信号 (2)如果该信号利用默认的处理程序处理,则由内核处理该信号 (3)否则 该信号由进程自己的处理程序处理,内核将修改当前进程的调用堆栈 并将进程的程序计数寄存器修改为信号处理程序的入口地址 此后,指令将跳转到信号处理程序,当从信号处理程序中返回时 实际就返回了进程的用户模式部分
信号产生
内核更新目标进程的数据结构以表示一个新信号已被发送 已经产生但还没有传递的信号称为挂起信号 一个进程仅存在给的类型的一个挂起信号 同一进程同类的其他信号不被排队 只是被简单丢弃 但实时信号(rt_sig) 同类型的挂起信号可以有多个
信号传递
内核强迫目标进程通过以下方式对信号做出反应: 或改变目标进程的执行状态 或开始执行一个特定的信号处理程序 或两者都是 只要信号被阻塞 它就不被传递 只有信号解除阻塞后才传递它 而一个被忽略的信号总是被传递(只是没有进一步操作) 信号是可消费资源 一旦它们已经传递出去 进程描述符中有关这个信号的所有信息都被取消
接口
清所有信号掩码的阻塞标志
int sigemptyset(sigset_t *mask)
设置所有信号掩码的阻塞标志
int sigfillset(sigset_t *mask, int signum)
删除个别信号阻塞
int sigdelset(sigset_t *mask, int signum)
增加个别信号阻塞
int sigaddset(sigset_t *mask, int signum)
确定特定的信号是否在掩码中被标志为阻塞
int sigisnumber(sigset_t *mask, int signum)
改变和检查自己的信号掩码的值
int sys_sigprocmask(int how, sigset_t *set, sigset_t *oset)
检查是否有挂起的阻塞信号
int sigpending(sigset_t mask)
定义对信号的处理操作
int sigaction(sig,&handler,&oldhandler)
从信号返回
int sigreturn(&context)
发送信号到进程
int kill(pid_t pid, int sig)
数据结构
使用实例
#include <stdio.h> #include <signal.h> #include <unistd.h> int ctrl_c_count=0; void (* old_handler)(INT); void ctrl_c(int); main() { int c; old_handler = signal(SIGINT,ctrl_c); //设置信号处理程序 while ((c=getchar())! = '\n'); printf("ctrl-c count = %d\n",ctrl_c_count); (void) signal(SIGINT,old_handler); } void ctrl_c(int signum) { /*重新建立 SIGINT 信号和 ctrl_c 函数之间的联系 因为当信号出现时,用 signal()调用设置的信号处理程序被自动恢复为默认操作 使得随后的同一信号将只执行信号的默认操作*/ (void)signal(SIGINT,ctrl_c) ++ctrl_c; }
POSIX信号使用要求说明
信号处理程序必须在多线程应用的所有线程之间共享 每个线程必须有自己的挂起信号掩码和阻塞信号掩码
kill和sigqueue必须向所有的多线程应用而不是某个特殊的线程发送信号
每个发送给多线程应用的信号仅传送给一个线程 这个线程是由内核在从不会阻塞该信号的线程中随意选择出来的
如果向多线程应用发送了一个致命信号 那么内核将杀死该应用的所有线程 而不仅仅是杀死接收信号的那个线程
如果一个挂起信号被发送给了某个特定进程 那么这个信号是私有的 如果被发送给了整个线程组 它就是共享的
信号量
信号量(semaphore )实际是一个整数,它的值由多个进程进行测试(test)和设置(set) 就每个进程所关心的测试和设置操作而言,这两个操作是不可中断的,或称“原子”操作, 即一旦开始直到两个操作全部完成。 测试和设置操作的结果是:信号量的当前值和设置值相加,其和或者是正或者为负 根据测试和设置操作的结果,一个进程可能必须睡眠,直到有另一个进程改变信号量的值 信号量可用来实现所谓的“临界区”的互斥使用,临界区指同一时刻只能有一个进程执行其中代码的代码段 信号量的初值表示系统中资源的数目 每次P操作表示进程请求一个单位的资源 信号量减1 当信号量小于0时 表示资源已分配完毕 进程自我阻塞 如果信号量小于0 那么信号量的绝对值表示当前阻塞队列中进程的个数
数据结构
系统中每个信号量的数据结构sem
struct sem { int semval; /* 信号量的当前值 */ int sempid; /*在信号量上最后一次操作的进程识别号 * };
系统中表示信号量集合的数据结构semid_ds
struct semid_ds { struct ipc_perm sem_perm; /* IPC 权限 */ long sem_otime; /* 最后一次对信号量操作(semop)的时间 */ long sem_ctime; /* 对这个结构最后一次修改的时间 */ struct sem *sem_base; /* 在信号量数组中指向第一个信号量的指针 */ struct sem_queue *sem_pending; /* 待处理的挂起操作*/ struct sem_queue **sem_pending_last; /* 最后一个挂起操作 */ struct sem_undo *undo; /* 在这个数组上的 undo 请求 */ ushort sem_nsems; /* 在信号量数组上的信号量号 */ };
系统中每一信号量集合的队列结构sem_queue
struct sem_queue { struct sem_queue * next; /* 队列中下一个节点 */ struct sem_queue ** prev; /* 队列中前一个节点, *(q->prev) == q */ struct wait_queue * sleeper; /* 正在睡眠的进程 */ struct sem_undo * undo; /* undo 结构*/ int pid; /* 请求进程的进程识别号 */ int status; /* 操作的完成状态 */ struct semid_ds * sma; /*有操作的信号量集合数组 */ struct sembuf * sops; /* 挂起操作的数组 */ int nsops; /* 操作的个数 */ };
接口
创建一个新的信号量集合,或者存取一个已存在的集合
int semget ( key_t key, int nsems, int semflg )
PV 操作
int semop ( int semid, struct sembuf *sops, unsigned nsops)
执行在信号量集上的控制操作
int semctl ( int semid, int semnum, int cmd, union semun arg )
使用实例
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define TIME_OUT 2000 void *function1(void *arg); void *function2(void *arg); sem_t sem1,sem2; int main() { pthread_t tid1,tid2; int ret; ret = sem_init(&sem1,0,0); if( -1 == ret) { perror("sem_init"); } ret = sem_init(&sem2,0,0); if( -1 == ret) { perror("sem_init"); } ret = pthread_create(&tid1,NULL,function1,NULL); if( 0 != ret) { perror("pthread_create 1"); } ret = pthread_create(&tid2,NULL,function2,NULL); if( 0 != ret) { perror("pthreat_create 2"); } ret = pthread_join(tid1,NULL); if( 0 != ret) { perror("pthread_join1"); } ret = pthread_join(tid2,NULL); if( 0 != ret) { perror("pthread_join2"); } ret = sem_destroy(&sem1); if( -1 == ret ) { perror("sem_destory"); } ret = sem_destroy(&sem2); if( -1 == ret ) { perror("sem_destory"); } return 0; } void time_add_ms(struct timeval *time, uint ms) { time->tv_usec += ms * 1000; // 微秒 = 毫秒 * 1000 if(time->tv_usec >= 1000000) // 进位,1000 000 微秒 = 1 秒 { time->tv_sec += time->tv_usec / 1000000; time->tv_usec %= 1000000; } } void *function1(void *arg) { int i,ret; struct timespec timeout; struct timeval time; ret = sem_wait(&sem1); //p操作申请资源 if( -1 == ret) { perror("sem_wait"); } for(i = 1; i <=2; i++) { printf("sem1----ppppppppppppppppp----\n"); sleep(1); } ret = sem_post(&sem2); //v操作 if( -1 == ret ) { perror("sem_post"); } printf("sem2 -------vvvvvvvvvvvvvvvvvvv-------\n"); gettimeofday(&time, NULL); time_add_ms(&time, TIME_OUT); timeout.tv_sec = time.tv_sec; timeout.tv_nsec = time.tv_usec * 1000; printf("===time——out %d\n",sem_timedwait(&sem1, &timeout)); printf("sem1 time out -----ppppppppppppppppp----\n"); return NULL; } void *function2(void *arg) { int i,ret; for(i = 1; i <=2; i++) { printf("######ppppppppppppppppp######\n"); sleep(1); } ret = sem_post(&sem1);//v操作释放资源 if( -1 == ret ) { perror("sem_post"); } printf("sem2 ====vvvvvvvvvvvvvvvvvvv====\n"); ret = sem_wait(&sem2); //p操作申请资源 if( -1 == ret) { perror("sem_wait"); } for(i = 1; i <=3; i++) { printf("sem2 ====ppppppppppppppppp====\n"); sleep(1); } return NULL; }
消息队列
数据结构
消息缓冲区msgbuf
struct msgbuf { long mtype; /* 消息的类型,必须为正数 */ char mtext[1]; /* 消息正文 */ };
消息结构msg
struct msg { struct msg *msg_next; /* 队列上的下一条消息 */ long msg_type; /*消息类型*/ char *msg_spot; /* 消息正文的地址 */ short msg_ts; /* 消息正文的大小 */ };
消息队列结构msgid_ds
struct msqid_ds { struct ipc_perm msg_perm; struct msg *msg_first; /* 队列上第一条消息,即链表头*/ struct msg *msg_last; /* 队列中的最后一条消息,即链表尾 */ time_t msg_stime; /* 发送给队列的最后一条消息的时间 */ time_t msg_rtime; /* 从消息队列接收到的最后一条消息的时间 */ time_t msg_ctime; /* 最后修改队列的时间*/ ushort msg_cbytes; /*队列上所有消息总的字节数 */ ushort msg_qnum; /*在当前队列上消息的个数 */ ushort msg_qbytes; /* 队列最大的字节数 */ ushort msg_lspid; /* 发送最后一条消息的进程的 pid */ ushort msg_lrpid; /* 接收最后一条消息的进程的 pid */ };
接口
创建一个新的消息队列,或存取一个已经存在的队列
int msgget ( key_t key, int msgflg )
将一个新的消息写入队列
int msgsnd ( int msqid, struct msgbuf *msgp, int msgsz, int msgflg )
从消息队列中读取消息
int msgrcv ( int msqid, struct msgbuf *msgp, int msgsz, long mtype, int msgflg )
使用实例
接收进程
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <errno.h> typedef struct msgbuf { long mtype; /* message type, must be > 0 */ char mtext[BUFSIZ]; /* message data */ }msgq_t; const key_t g_msg_key = 1234; int main(int argc, char* argv[]) { int msg_handle, ret; int run_flag = 1; long int msgtype = -5; msgq_t data; msg_handle = msgget(g_msg_key, IPC_PRIVATE | IPC_CREAT); if(msg_handle == -1) { printf("msgget failed with error %d\n", errno); exit(EXIT_FAILURE); } while (run_flag) { ret = msgrcv(msg_handle, &data, BUFSIZ, msgtype, 0); if(ret == -1) { printf("msgrcv failed with error %d\n", errno); exit(EXIT_FAILURE); } else { printf("recv queue type %ld, data: %s\n", data.mtext, data.mtext); } if(strncmp(data.mtext, "end", 3) == 0) { run_flag = 0; } } ret = msgctl(msg_handle, IPC_RMID, 0); if(ret == -1) { printf("msgctl failed with error %d\n", errno); exit(EXIT_FAILURE); } return 0; }
发送进程
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <errno.h> #define SENDBUF 1024 typedef struct msgbuf { long mtype; /* message type, must be > 0 */ char mtext[SENDBUF]; /* message data */ }msgq_t; const key_t g_msg_key = 1234; int main(int argc, char* argv[]) { int msg_handle, ret, run_flag = 1; msgq_t data; char sendbuf[SENDBUF]; long int msgtype = 1; msg_handle = msgget(g_msg_key, IPC_PRIVATE | IPC_CREAT); if (msg_handle == -1) { printf("msgget failed with error %d\n", errno); exit(EXIT_FAILURE); } while (run_flag) { printf("Please input msgtype and msg\n"); scanf("%ld%s", &msgtype, sendbuf); data.mtype = msgtype; strcpy(data.mtext, sendbuf); ret = msgsnd(msg_handle, &data, sizeof(msgq_t) - sizeof(long), 0); if(ret == -1) { printf("msgsnd failed with error %d\n", errno); exit(EXIT_FAILURE); } if (strncmp(sendbuf, "end", 3) == 0) { run_flag = 0; } } ret = msgctl(msg_handle, IPC_RMID, 0); if(ret == -1) { printf("msgctl failed with error %d\n", errno); exit(EXIT_FAILURE); } return 0; }
共享内存
数据结构
struct shmid_ds { struct ipc_perm shm_perm; /* 操作权限 */ int shm_segsz; /* 段的大小(以字节为单位) */ time_t shm_atime; /* 最后一个进程附加到该段的时间 */ time_t shm_dtime; /* 最后一个进程离开该段的时间 */ time_t shm_ctime; /* 最后一次修改这个结构的时间 */ unsigned short shm_cpid; /*创建该段进程的 pid */ unsigned short shm_lpid; /* 在该段上操作的最后一个进程的 pid */ short shm_nattch; /*当前附加到该段的进程的个数 */ /* 下面是私有的 */ unsigned short shm_npages; /*段的大小(以页为单位) */ unsigned long *shm_pages; /* 指向 frames -> SHMMAX 的指针数组 */ struct vm_area_struct *attaches; /* 对共享段的描述 */ };
原理说明
进程第 1 次访问共享虚拟内存时将产生缺页异常 Linux 找出描述该内存的vm_area_struct 结构 该结构中包含用来处理这种共享虚拟内存段的处理函数地址 共享内存缺页异常处理代码对 shmid_ds 的页表项表进行搜索,以便查看是否存在该共享虚拟内存的页表项 如果没有,系统将分配一个物理页并建立页表项,该页表项加入 shmid_ds 结构的同时也添加到进程的页表中 (意味着当下一个进程试图访问这页内存时出现缺页异常,共享内存的缺页异常处理代码则把新创建的物理页给这个进程) 第 1 个进程对共享内存的存取引起创建新的物理页面 而其他进程对共享内存的存取引起把那个页添加到它们的地址空间 当某个进程不再共享其虚拟内存时,利用系统调用将共享段从自己的虚拟地址区域中移去,并更新进程页表 当最后一个进程释放了共享段之后,系统将释放给共享段所分配的物理页 当共享的虚拟内存没有被锁定到物理内存时,共享内存也可能会被交换到交换区中
接口
得到一个共享内存标识符或创建一个共享内存对象
int shmget ( key_t key, int size, int shmflg )
把共享内存区对象映射到调用进程的地址空间
int shmat(int shmid, char *shmaddr, int shmflg)
断开共享内存连接
int shmdt(const void *shmaddr)
对共享内存的控制
int shmctl ( int shmqid, int cmd, struct shmid_ds *buf )
使用实例
#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/ipc.h> #include <sys/shm.h> #include "error.h" #define SIZE 1024 int main() { int shmid ; char *shmaddr ; struct shmid_ds buf ; int pid ; shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT|0600 ) ; if ( shmid < 0 ) { perror("get shm ipc_id error") ; return -1 ; } pid = fork() ; if ( pid == 0 ) { shmaddr = (char *)shmat( shmid, NULL, 0 ) ; if ( (long)shmaddr == -1 ) { perror("shmat addr error") ; return -1 ; } strcpy( shmaddr, "Hi, I am child process!\n") ; shmdt( shmaddr ) ; return 0; } else if ( pid > 0) { sleep(3 ) ; int flag = shmctl( shmid, IPC_STAT, &buf) ; if ( flag == -1 ) { perror("shmctl shm error") ; return -1 ; } printf("shm_segsz =%ld bytes\n", buf.shm_segsz ) ; printf("parent pid=%d, shm_cpid = %d \n", getpid(), buf.shm_cpid ) ; printf("chlid pid=%d, shm_lpid = %d \n",pid , buf.shm_lpid ) ; shmaddr = (char *) shmat(shmid, NULL, 0 ) ; if ( (long)shmaddr == -1 ) { perror("shmat addr error") ; return -1 ; } printf("%s", shmaddr) ; shmdt( shmaddr ) ; shmctl(shmid, IPC_RMID, NULL) ; }else{ perror("fork error") ; shmctl(shmid, IPC_RMID, NULL) ; } return 0 ; }
进程数据结构
进程描述符结构
ID
PID
进程的唯一标识
PGID
进程组ID。 每个进程都会有进程组ID, 表示该进程所属的进程组 默认情况下新创建的进程会继承父进程的进程组ID
SID
会话ID。 每个进程也都有会话ID 默认情况下, 新创建的进程会继承父进程的会话ID
创建会话setsid
这个函数的调用进程不是进程组组长, 那么调用该函数会发生以下事情 1) 创建一个新会话, 会话ID等于进程ID, 调用进程成为会话的首进程 2) 创建一个进程组, 进程组ID等于进程ID, 调用进程成为进程组的组长 3) 该进程没有控制终端, 如果调用setsid前, 该进程有控制终端, 这种联系就会断掉
4种堆栈
系统引导初始化时临时使用的堆栈
bootsect中设置的堆栈
进入保护模式后提供内核程序初始化使用的堆栈
位于内核代码地址空间固定位置处 该堆栈也是后来任务0使用的用户态堆栈
每个任务通过系统调用 执行内核程序时使用的堆栈
称为 任务的内核态堆栈 每个任务都有自己独有的内核态堆栈
任务在用户态执行的堆栈
位于任务逻辑地址空间近末端处
调试
LD_DEBUG=all ./执行程序
环境变量
DT_NEED
DT_RPATH
LD_LIBRARY_PATH
LD_RUNPATH
LD_HWCAP_MASK
用于屏蔽掉一些硬件功能选项
strace
线程
线程控制块TCB
线程标识符
寄存器
程序计数器
状态寄存器
通用寄存器
线程执行状态
优先级
线程专有存储区
用于在线程切换时存放线程保护信息和与该线程相关的统计信息
信号屏蔽信息
堆栈指针
保存局部变量和返回地址
内核支持线程KST
优点
多处理机系统中 内核能够同时调度同一进程中的多个线程并行运行
当进程中的一个线程被阻塞 则内核可以调度该进程中的其他线程来占有处理器并运行 也可以运行其他进程中的线程
内核支持线程具有很小的数据结构和堆栈 线程的切换比较块 切换开销小
内核本身也可以采样多线程技术 提高系统的执行速度和效率
缺点
模式切换系统开销变大 在同一进程中 从一个线程切换到另一个线程时需要从用户态转到内核态进行(线程的调度和管理时在内核中实现的)
用户级线程ULT
优点
线程切换不需要转换到内核空间
调度算法可以定制 不同的进程可以根据自身需要选择不同的调度算法 以对自己的线程进行管理和调度 而与OS的低级调度算法无关
用户级线程的实现与平台无关 面向线程管理的代码属于用户程序 所有的应用程序可以共享
缺点
系统调用的阻塞问题 当线程执行一个系统调用时 不仅该线程会被阻塞 所属进程内所有线程均会阻塞
多线程应用不能利用多处理机进行多重处理 内核每次分配给一个进程的仅有一个CPU 进程中仅有一个线程能执行 在该线程放弃CPU之前 其他线程只能等待
混合线程(ULT&KST)
多对一模型
将多个ULT映射到一个KST上 这些ULT一般属于一个进程 运行在该进程的用户空间 对这些线程的调度和管理也是在该进程的用户空间中完成 仅当ULT需要访问内核时 才将其映射到一个KST上 但每次只运行一个线程进行映射
优点
线程管理的开销小 效率高
缺点
如果一个线程在访问内核时发生阻塞 则整个进程都会被阻塞
任一时刻 只有一个线程能够访问内核 多个线程不能同时在多个处理器上运行
一对一模型
将每个ULT都映射到KST上
优点
当一个线程阻塞时 允许调度另一个线程运行 提供了比多对一模型更好的并发性能
缺点
每创建一个ULT相应的就需要创建一个KST 开销大 因此需要限制整个系统的线程数
多对多模型
将许多ULT映射到较少数量的KST上 可以像1对1模型那样使一个进程的多个线程并行运行 也可以减少线程管理开销并提高效率