操作系统有两个基本上独立的任务。首先,作为扩展机器的操作系统,其职责是将硬件提供的丑陋接口转化,然后给使用者提供美丽的接口;其次,作为资源管理者的操作系统,它的任务又是在相互竞争的程序之间有序的控制对处理器、内存、磁盘、I/O设备的分配。
系统概念模型
对于linux操作系统而言,它的核心部分是内核,在内核中主要提供了进程管理,内存管理,文件管理,I/O设备管理功能。从宏观上看,linux操作系统的体系架构被分为用户态和内核态。用户态实际上是供上层的应用使用操作系统的活动空间,内核态实际上是CPU的特权模式,是CPU的一种工作状态,它影响CPU对不同指令的执行结果。
进程管理
进程本质上是程序+执行所需资源,内核负责进程的创建和销毁,并且由调度程序采取合适的调度策略,实现进程间的合理的且实时的处理器资源的共享。从而内核的进程管理活动实现了多个进程在一个或多个处理器上的抽象。内核还负责实现进程间的通信以及进程和其他部件之间的通信。
进程在linux中的实现是task_struct结构体,记录了进程运行过程中寄存器内的信息、使用到的资源、状态等信息;同时,为了能够快速访问其他进程,结构体内部还有指向父进程的指针,以及该进程孩子、兄弟的双向链表指针等。
内存管理
内存是非常重要的资源。内核为每个进程建立一个虚拟地址空间。内存管理部分代码可分为硬件无关部分和硬件相关部分:硬件无关部分实现进程和内存之间的地址映射等功能;硬件相关部分实现不同体系结构上的内存管理相关功能并为内存管理提供与硬件无关的虚拟接口。
文件管理
文件本身实际上是保存在磁盘上的二进制序列,linux中,文件有如下几种类型,分别是正规文件、目录文件、符号链接、设备文件、管道文件以及套接字。为了组织管理文件,linux引入虚拟文件系统VFS,对各种不同文件系统的操作和管理纳入到一个统一的框架中,VFS主要由四个类型的对象组成,分别为超级块对象、文件对象、索引节点对象以及目录项对象。在进程与文件的交互中,每个进程都有文件对象,文件对象指向硬链接,硬链接又会对应目录项对象,目录项对象又对应索引节点对象,索引节点对象可以标识超级块对象和普通磁盘文件。
I/O设备分配
Linux系统中几乎每个系统操作终都映射到一个或多个物理设备上。 除了处理器、内存等少数的硬件资源之外,任何一种设备控制操作都由设备特定的驱动代码来进行。内核中必须提供系统中可能要操作的每一种外设的驱动。
程序实例分析
下面的程序功能是使用两个线程按每次增加1的顺序从0交替打印到指定的数字。
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<sys/types.h>
#include<pthread.h>
#include<semaphore.h>
#include<time.h>
#define DEFAULT_END_VALUE 100000
//当前需要打印的值
long finalValue,currValue;
sem_t sem_odd , sem_even;
pthread_mutex_t mutex;
void init();
void end();
long getLong(char *s);
int isNumber(int v);
void* thread_odd_handler(void* args);
void* thread_even_handler(void* args);
int main(int argc , char *argv[])
{
if(argc > 2){
printf("argument can only one input.\n");
exit(EXIT_SUCCESS);
}
finalValue = argc == 2 ? getLong(argv[1]) : DEFAULT_END_VALUE;
init();
//两个需要交替打印奇数与偶数的线程
pthread_t threadId1, threadId2;
if(pthread_create(&threadId1,NULL ,thread_odd_handler,NULL) != 0 || pthread_create(&threadId2,NULL ,thread_even_handler , NULL) != 0){
perror("create thread failure.\n");
exit(EXIT_SUCCESS);
}
if(pthread_join(threadId1,NULL) != 0){
perror("thread1 join error");
exit(EXIT_SUCCESS);
}
if(pthread_join(threadId2,NULL) != 0){
perror("thread2 join error");
exit(EXIT_SUCCESS);
}
end();
return 0;
}
void init()
{
currValue = 0;
if(sem_init(&sem_odd , 0 , 1) != 0 || sem_init(&sem_even , 0 ,1) != 0){
perror("semaphore init error.\n");
exit(EXIT_SUCCESS);
}
if(sem_wait(&sem_odd) != 0){
perror("odd semaphore P error.\n");
exit(EXIT_SUCCESS);
}
if(pthread_mutex_init(&mutex , NULL) != 0){
perror("mutex init error.\n");
exit(EXIT_SUCCESS);
}
}
void end()
{
if(sem_destroy(&sem_odd) != 0 || sem_destroy(&sem_even) != 0){
perror("semaphore destory error.\n");
exit(EXIT_SUCCESS);
}
if(pthread_mutex_destroy(&mutex) != 0){
perror("mutex destory error.\n");
exit(EXIT_SUCCESS);
}
}
long getLong(char *s)
{
long res = 0;
int i , currNum;
for(i = 0 ; s[i] != ‘\0‘ ; i++){
if(!isNumber(currNum = (s[i] - ‘0‘))){
perror("input error: must be a number.\n");
exit(EXIT_SUCCESS);
}
res = (res << 3) + (res << 1);
res += currNum;
}
if(res < 0){
perror("input number is too big.\n");
exit(EXIT_SUCCESS);
}
return res;
}
int isNumber(int v){
return v >= 0 && v <= 9;
}
void* thread_odd_handler(void* args)
{
int cont = 1;
while (cont)
{
if(sem_wait(&sem_odd) != 0){
perror("sem_odd wait error.\n");
exit(EXIT_SUCCESS);
}
if(pthread_mutex_lock(&mutex) != 0){
perror("mutex lock error.\n");
exit(EXIT_SUCCESS);
}
if(currValue <= finalValue){
printf("%ld\t" , currValue++);
}else{
cont = 0;
}
if(pthread_mutex_unlock(&mutex) != 0){
perror("mutex unlock error.\n");
exit(EXIT_SUCCESS);
}
if(sem_post(&sem_even) != 0){
perror("sem_even post error.\n");
exit(EXIT_SUCCESS);
}
}
return NULL;
}
void* thread_even_handler(void* args)
{
int cont = 1;
while (cont)
{
if(sem_wait(&sem_even) != 0){
perror("sem_even wait error.\n");
exit(EXIT_SUCCESS);
}
if(pthread_mutex_lock(&mutex) != 0){
perror("mutex lock error.\n");
exit(EXIT_SUCCESS);
}
if(currValue <= finalValue){
printf("%ld\t" , currValue++);
}else{
cont = 0;
}
if(pthread_mutex_unlock(&mutex) != 0){
perror("mutex unlock error.\n");
exit(EXIT_SUCCESS);
}
if(sem_post(&sem_odd) != 0){
perror("sem_odd post error.\n");
exit(EXIT_SUCCESS);
}
}
return NULL;
}
下面是不使用线程直接打印的程序
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<sys/types.h>
#define DEFAULT_END_VALUE 100000
long finalValue;
void singleThreadPrint(){
long i;
for(i = 0 ; i <= finalValue ; i++){
printf("%ld\t",i);
}
}
int isNumber(int v){
return v >= 0 && v <= 9;
}
long getLong(char *s)
{
long res = 0;
int i , currNum;
for(i = 0 ; s[i] != ‘\0‘ ; i++){
if(!isNumber(currNum = (s[i] - ‘0‘))){
perror("input error: must be a number.\n");
exit(EXIT_SUCCESS);
}
res = (res << 3) + (res << 1);
res += currNum;
}
if(res < 0){
perror("input number is too big.\n");
exit(EXIT_SUCCESS);
}
return res;
}
int main(int argc , char *argv[])
{
if(argc > 2){
printf("argument can only one input.\n");
exit(EXIT_SUCCESS);
}
finalValue = argc == 2 ? getLong(argv[1]) : DEFAULT_END_VALUE;
singleThreadPrint();
return 0;
}
下面是测试结果
$ time ./alternatePrint > result.txt
real 0m1.433s
user 0m0.008s
sys 0m1.527s
$ time ./singlePrint > resultSingle.txt
real 0m0.007s
user 0m0.006s
sys 0m0.000s
可以看到当代码中使用互斥锁、信号量时,性能会明显下降。
除此之外,cpu的时钟频率、cache的容量、内存的容量、磁盘的存取速度及网络带宽大小等硬件资源都会影响系统的性能。然后应用程序本身的执行过程也会影响程序的执行性能,而遵循时间局部性和空间局部性的程序往往能过得更高的执行性能。
总结
通过学习这门课程,让我对Linux操作系统有了更深的了解。感谢孟老师和李老师的辛苦付出, 让我们学习到了很多。