Linux内核
操作系统是什么
内核是什么
从功能层面上来说,内核就是一个中间层,软件和硬件之间交互的中间层,链接层
从其他方面理解内核 系统调用,开放了很多接口;资源管理
内核实现的策略
宏内核
微内核
内核包含哪些核心的模块
进程的调度与切换
内存管理
虚拟内存机制
和网络交互的地方
设备驱动程序
进程通信机制&锁
其他模块
Linux进程调度
进程分类
实时进程 用户交互的进程,需要及时响应
普通进程 如压缩文件,视频的编码解码
上下文切换
上下文切换指的是内核在CPU上对进程或者线程进行切换。上下文切换过程中的信息被保存在进程控制块(PCB-Process Control Block)中。PCB又被称作切换帧(SwitchFrame)。上下文切换的信息会一直被保存在CPU的内存中,直到被再次使用。
执行环境的变化
调度算法
FCFS 先来先服务调度算法
SJF 短作业(进程)优先调度算法
SRTF 最短剩余时间优先算法
HRRF 响应比高者优先算法
PS 优先级调度算法
RR 时间片轮转法
MLFQ 多级反馈队列调度算法
进程队列
全局队列
局部队列
进程优先级
查看进程优先级的命令 ps -l
nice值
nice值越高,优先级越低
用户可以设置的值,这个值是来影响优先级
Linux进程优先级和nice值 - leno米雷 - 博客园 (cnblogs.com)
优先级和nice的范围
nice -20~19
进程优先级0~139
实时进程的优先级是0~100
普通进程的优先级是100~139
静态优先级
nice值来决定的优先级,权利在用户,用户在一开始就设定优先级,同时可以更改
动态优先级
内核来决定的,内核自动修改优先级
Linux调度器
O(n)调度器 cpu进程队列
O(1)调度器 优先级映射成了一个bitmap
CFS调度器
【原创】(一)Linux进程调度器-基础 - LoyenWang - 博客园 (cnblogs.com)
linux调度器源码分析 - 概述(一) - tolimit - 博客园 (cnblogs.com)
进程通信
- 管道 分为无名管道pipe和 命名管道named pipe
- 消息队列 message
- 信号 signal
- 共享内存 shared memory
- 信号量 semaphore
- 套接字 socket 可用于不同系统之间,管道和消息队列只能在同一系统
进程间通讯的7种方式_zhaohong_bo的专栏-优快云博客_进程间通信
信号
软中断信号(signal,又简称为信号)用来通知进程发生了事件。
注意,信号只是用来通知某进程发生了什么事件,无法给进程传递任何数据,进程对信号的处理方法有三种:
1)第一种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。
2)第二种是设置中断的处理函数,收到信号后,由该函数来处理。
3)第三种方法是,对该信号的处理采用系统的默认操作,大部分的信号的默认操作是终止进程。
signal库函数
signal库函数可以设置程序对信号的处理方式。
函数声明:
sighandler_t signal(int signum, sighandler_t handler);
参数signum表示信号的编号。
参数handler表示信号的处理方式,有三种情况:
1)SIG_IGN:忽略参数signum所指的信号。
2)一个自定义的处理信号的函数,信号的编号为这个自定义函数的参数。
3)SIG_DFL:恢复参数signum所指信号的处理方法为默认值。
程序员不关心signal的返回值。
发送信号
Linux操作系统提供了kill命令向程序发送信号,C语言也提供了kill库函数,用于在程序中向其它进程或者线程发送信号。
函数声明:
int kill(pid_t pid, int sig);
kill函数将参数sig指定的信号给参数pid 指定的进程。
参数pid 有几种情况:
1)pid>0 将信号传给进程号为pid 的进程。
2)pid=0 将信号传给和目前进程相同进程组的所有进程,常用于父进程给子进程发送信号,注意,发送信号者进程也会收到自己发出的信号。
3)pid=-1 将信号广播传送给系统内所有的进程,例如系统关机时,会向所有的登录窗口广播关机信息。
sig:准备发送的信号代码,假如其值为零则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为零来检验某个进程是否仍在运行。
返回值说明: 成功执行时,返回0;失败返回-1,errno被设为以下的某个值。
EINVAL:指定的信号码无效(参数 sig 不合法)。
EPERM:权限不够无法传送信号给指定进程。
ESRCH:参数 pid 所指定的进程或进程组不存在。
共享内存
共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。
共享内存并未提供锁机制,也就是说,在某一个进程对共享内存的进行读写的时候,不会阻止其它的进程对它的读写。如果要对共享内存的读/写加锁,可以使用信号灯。
shmget函数
shmget函数用来获取或创建共享内存,它的声明为:
int shmget(key_t key, size_t size, int shmflg);
参数key是共享内存的键值,是一个整数,typedef unsigned int key_t,是共享内存在系统中的编号,不同共享内存的编号不能相同,这一点由程序员保证。key用十六进制表示比较好。
参数size是待创建的共享内存的大小,以字节为单位。
参数shmflg是共享内存的访问权限,与文件的权限一样,0666|IPC_CREAT表示全部用户对它可读写,如果共享内存不存在,就创建一个共享内存。
shmat函数
把共享内存连接到当前进程的地址空间。它的声明如下:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
参数shm_id是由shmget函数返回的共享内存标识。
参数shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
参数shm_flg是一组标志位,通常为0。
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
shmdt函数
该函数用于将共享内存从当前进程中分离,相当于shmat函数的反操作。它的声明如下:
int shmdt(const void *shmaddr);
参数shmaddr是shmat函数返回的地址。
调用成功时返回0,失败时返回-1.
shmctl函数
删除共享内存,它的声明如下:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
参数shm_id是shmget函数返回的共享内存标识符。
参数command填IPC_RMID。
参数buf填0。
解释一下,shmctl是控制共享内存的函数,其功能不只是删除共享内容,但其它的功能没什么用,所以不介绍了。
注意,用root创建的共享内存,不管创建的权限是什么,普通用户无法删除。
用ipcs -m可以查看系统的共享内存,内容有键值(key),共享内存编号(shmid),创建者(owner),权限(perms),大小(bytes)。
用ipcrm -m 共享内存编号,可以手工删除共享内存
信号量
信号量(信号灯)本质上是一个计数器,用于协调多个进程(包括但不限于父子进程)对共享数据对象的读/写。它不以传送数据为目的,主要是用来保护共享资源(共享内存、消息队列、socket连接池、数据库连接池等),保证共享资源在一个时刻只有一个进程独享。
semget函数
semget函数用来获取或创建信号量,它的原型如下:
int semget(key_t key, int nsems, int semflg);
semctl函数
该函数用来控制信号量(常用于设置信号量的初始值和销毁信号量),它的原型如下:
int semctl(int semid, int sem_num, int command, ...);
semop函数
该函数有两个功能:1)等待信号量的值变为1,如果等待成功,立即把信号量的值置为0,这个过程也称之为等待锁;2)把信号量的值置为1,这个过程也称之为释放锁。
int semop(int semid, struct sembuf *sops, unsigned nsops);
多线程
和多进程相比,多线程是一种比较节省资源的多任务操作方式。
启动一个新的进程必须分配给它独立的地址空间,每个进程都有自己的堆栈段和数据段,系统开销比较高,进行数据的传递只能通过进行间通信的方式进行。
在同一个进程中,可以运行多个线程,运行于同一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享全局变量和对象,启动一个线程所消耗的资源比启动一个进程所消耗的资源要少。
创建线程
在Linux下,采用pthread_create函数来创建一个新的线程,函数声明:
函数声明:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数thread为为指向线程标识符的地址。
参数attr用于设置线程属性,一般为空,表示使用默认属性。
参数start_routine是线程运行函数的地址,填函数名就可以了。
参数arg是线程运行函数的参数。新创建的线程从start_routine函数的地址开始运行,该函数只有一个无类型指针参数arg。若要想向start_routine传递多个参数,可以将多个参数放在一个结构体中,然后把结构体的地址作为arg参数传入,但是要非常慎重,程序员一般不会这么做。
在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库。
线程的终止
如果进程中的任一线程调用了exit,则整个进程会终止,所以,在线程的start_routine函数中,不能采用exit。
线程的终止有三种方式:
1)线程的start_routine函数代码结束,自然消亡。
2)线程的start_routine函数调用pthread_exit结束。
3)被主进程或其它线程中止。
pthread_exit函数的声明如下:
void pthread_exit(void *retval);
参数retval填空,即0。
线程同步
对多线程来说,资源是共享的,基本上不存在不允许访问的情况,但是,共享的资源在某一时间点只能有一个线程占用,所以需要给资源加锁。
线程的锁的种类有互斥锁、读写锁、条件变量、自旋锁、信号灯。
互斥锁
互斥锁机制是同一时刻只允许一个线程占有共享的资源
初始化锁
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);
阻塞加锁
int pthread_mutex_lock(pthread_mutex *mutex);
非阻塞加锁
int pthread_mutex_trylock( pthread_mutex_t *mutex);
解锁
int pthread_mutex_unlock(pthread_mutex *mutex);
销毁锁(此时锁必需unlock状态,否则返回EBUSY)
int pthread_mutex_destroy(pthread_mutex *mutex);
协程
协程,又称微线程,纤程。英文名Coroutine。
C/C++ 语言本身是不能天然支持协程的。
现有的 C++ 协程库均基于两种方案:利用汇编代码控制协程上下文的切换,以及利用操作系统提供的 API 来实现协程上下文切换。