OS代码

Linux内核模块

一:创建内核模块

lsmod  //列出当前加载的所有内核模块
//这个命令会采用三列来列出当前的内核模块:名称、大小和正在使用的模块

modinfo simple.ko //获得模块的信息
//simple.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

//printk是等价于printf的内核函数,然而它的输出被发送到一个内核日志缓冲区,其内容可以通过dmesg命令来读取。printk()与printf()之间的一个区别是:printk()允许指定一个优先级,具体值由文件<linux/printk.h>来定义。在这里,优先级为KERN_INFO,表示这是一个信息性(informational)消息

int simple_init(void){
    printk(KERN_INFO "start\n");
    return 0;
}

void simple_exit(void){
    printk(KERN_INFO "end\n");
}

//下面两个宏用于向内核注册模块的入口点和退出点,都不能传递任何参数

//模块的入口点函数,当模块加载到内核时被调用
//返回0表示成功,其它表示失败
module_init(simple_init);

//模块的退出点,当模块从内核中移除时被调用
module_exit(simple_exit);

//下面三行说明软件许可、模块描述、作者等信息。对于我们而言,不会用到这些信息,但是我们包括它,因为这是开发内核模块的标准做法

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple Module");
MODULE_AUTHOR("zzy");

这个内核模块simple.c的编译,可采用Makefile文件

//Makefile

KVERS = $(shell uname -r)
#shell中输出uname -r会获得内核版本号,这里将版本号存到变量KVERS
# Kernel modules
#将后面的东东编译为“内核模块”, obj-y 编译进内核,obj-n 不编译。
obj-m += hello.o

# 开启EXTRA_CFLAGS=-g-O0,可以得到包含调试信息的hello.ko模块。
#EXTRA_CFLAGS=-g -O0
#-C 表示让 make 命令进入指定的内核源代码目录
build: kernel_modules
kernel_modules:
	make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules

clean:
	make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

输入以下命令行:

make //编译生成多个文件,文件simple.ko为已编译的内核模块。接下来的步骤用于将这个模块插入Linux内核

sudo insmod simple.ko //加载内核模块

lsmod //查看模块是否被加载

dmesg //检查内核日志缓冲区,检查模块是否已经加载

sudo rmmod simple //删除内核模块

sudo dmesg -c //由于内核日志缓冲区可能很快填满,通常最好定期清除缓冲区,可以这样进行

管道通信

Unix普通管道

父进程创建了一个管道,并写入数据,子进程读数据

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1

int main(){
	char write_msg[BUFFER_SIZE] = "Greetings";
	char read_msg[BUFFER_SIZE];
	// pipe(int fd[2]):创建一个管道,以便通过文件描述符int fd[]来访问;fd[0]为管道的读出端,而fd[1]为管道的写入端。UNIX将管道作为一种特殊类型的文件。因此,访问管道可以采用普通的系统调用read()和write()
	int fd[2];
	pid_t pid;
	pipe(fd);
	pid=fork();
	if(pid>0){
		close(fd[READ_END]);
		write(fd[WRITE_END],write_msg,strlen(write_msg)+1);
		close(fd[WRITE_END]);
	}
	else{
		close(fd[WRITE_END]);
		read(fd[READ_END],read_msg,strlen(read_msg)+1);
		printf("%s\n",read_msg);
		close(fd[READ_END]);
	}
} 

pthread多线程

pthread示例程序

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

int sum; // this data is shared by the thread(s)
void *runner(void *param); // threads call this function

int main(int argc,char *argv[]){
    pthread_t tid; // the thread identifier
    pthread_attr_t attr; // set of thread attributes

    if(argc!=2){
        fprintf(stderr,"usage: a.out <integer value>\n");
    }
    if(atoi(argv[1])<0){
        fprintf(stderr,"%d must be >= 0\n",atoi(argv[1]));
    }
    // get the default attributes
    pthread_attr_init(&attr);
    // create the thread
    pthread_create(&tid,&attr,runner,argv[1]);
    // wait for the thread to exit
    pthread_join(tid,NULL);
    printf("sum = %d\n",sum);
    return 0;
}

// The thread will begin control in this function
void *runner(void *param){
    int i,upper = atoi(param);
    sum=0;
    for(i=1;i<=upper;i++)
        sum+=i;
    pthread_exit(0);
}

所有的Pthreads程序都要包括头文件pthread.h。语句pthread_t tid声明了创建线程的标识符。每个线程都有一组属性,包括堆栈大小和调度信息。声明pthread_attr_t attr表示线程属性;通过调用函数pthread_attr_init(&attr)可以设置这些属性。由于没有明确设置任何属性,所以使用缺省属性。通过调用函数pthread_create()可以创建一个单独线程。除了传递线程标识符和线程属性外,还要传递函数名称,这里为runner(),以便新线程可以开始执行这个函数。最后,还要传递由命令行参数argv[1]提供的整形参数。

在创建了累加和线程之后,父线程通过调用pthread_join()函数等待runner()线程的完成。累加和线程在调用了函数pthread_exit()之后就会终止。一旦累加和线程返回,父线程就输出累加和的值。

注:由于pthread不是Linux下的默认的库,也就是在链接的时候,无法找到phread库中哥函数的入口地址,于是链接会失败,因此在gcc编译的时候,要加 -lpthread参数

gcc pthread.c -o pthread -lphread

通过pthread_join()等待多个线程的一个简单方法:将这个操作包含在一个简单的for循环中。

#define NUM_THREADS 10

// an array of threads to be joined upon
pthread_t workers[NUM_THREADS];

for(int i=0;i<NUM>THREADS;i++){
	pthread_join(workers[i],NULL);
}

多线程冲突案例

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

long long sum; // this data is shared by the thread(s)
void *runner(void *param); // threads call this function
int N = 100000;

int main(int argc,char *argv[]){
    pthread_t tid1; // the thread identifier
    pthread_t tid2;
    pthread_attr_t attr1;
    pthread_attr_t attr2;
    pthread_attr_init(&attr2);
    // get the default attributes
    pthread_attr_init(&attr1);
    // create the thread
    pthread_create(&tid1,&attr1,runner,NULL);
    pthread_create(&tid2,&attr2,runner,NULL);
    // wait for the thread to exit
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    printf("sum = %lld\n",sum);
    return 0;
}

void *runner(void *param){
    for(int i=0;i<N;i++){
        sum++;
    }
    pthread_exit(0);
}

输出:

sum = 131801
sum = 133510
sum = 105389
sum = 114163
sum = 161298
sum = 111119
sum = 119842
sum = 152644

而正确答案应该是:200000

多线程冲突案例解决方法(Peterson算法)

使用Peterson算法

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define BARRIER __sync_synchronize()
long long sum; // this data is shared by the thread(s)
void *runner1(void *param); // threads call this function
void *runner2(void *param);
int N = 30000000;
bool flagA=0;
bool flagB=0;
char door_flag;

int main(int argc,char *argv[]){
    pthread_t tid1;
    pthread_t tid2;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_t attr2;
    pthread_attr_init(&attr2);
    // create the thread
    pthread_create(&tid1,&attr,runner1,NULL);
    pthread_create(&tid2,&attr2,runner2,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    printf("sum = %lld\n",sum);
    return 0;
}

void *runner1(void *param){
    for(int i=0;i<N;i++){
        flagA=1;BARRIER; //保证flagA=1在door_flag='B'之前执行 
        door_flag='B';
        while(door_flag=='B'&&flagB==1);
        sum++;//BARRIER; 按照常理应该也要保证sum++在flagA之前执行,但不知道为什么这里不加BARRIER执行顺序也不会出错 
        flagA=0;
    }
    pthread_exit(0);
}

void *runner2(void *param){
    for(int i=0;i<N;i++){
        flagB=1;BARRIER;
        door_flag='A';
        while(door_flag=='A'&&flagA==1);
        sum++;//BARRIER;
        flagB=0;
    }
}

通过全局变量读取另一个线程局部变量

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void *runner1(void *param);

void *runner2(void *param); // threads call this function

int *tem;

int main(int argc,char *argv[]){
    pthread_t tid1; // the thread identifier
    pthread_t tid2;
    pthread_attr_t attr1;
    pthread_attr_t attr2;
    pthread_attr_init(&attr2);
    // get the default attributes
    pthread_attr_init(&attr1);
    // create the thread
    int fa_tem = 0;
    tem = &fa_tem;
    printf("main's tem = %d\n",*tem);
    pthread_create(&tid1,&attr1,runner1,NULL);
    pthread_create(&tid2,&attr2,runner2,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    return 0;
}

void *runner1(void *param){
    int tem1 = 5;
    tem = &tem1;
    printf("runner1's tem = %d\n",*tem);
    sleep(3);
    pthread_exit(0);
}

void *runner2(void *param){
    sleep(1);
    printf("runner read runner1's local tem = %d\n",*tem);
    pthread_exit(0);
}

输出结果:

main's tem = 0
runner1's tem = 5
runner read runner1's local tem = 5

自旋锁

自旋锁实现sum

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

long long sum;
void *runner1(void *param);
void *runner2(void *param);
int N = 300000;
int self_lock;

int xchg(volatile int *addr, int newval) {
  int result;
  asm volatile ("lock xchg %0, %1"
    : "+m"(*addr), "=a"(result) : "1"(newval));
  return result;
}

int main(int argc,char *argv[]){
    pthread_t tid1;
    pthread_t tid2;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_t attr2;
    pthread_attr_init(&attr2);
    pthread_create(&tid1,&attr,runner1,NULL);
    pthread_create(&tid2,&attr2,runner2,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    printf("sum = %lld\n",sum);
    return 0;
}

void lock(){while(xchg(&self_lock,1));}
void unlock(){while(!xchg(&self_lock,0));}

void *runner1(void *param){
    for(int i=0;i<N;i++){
        lock();
        sum++;
        unlock();
    }
    pthread_exit(0);
}

void *runner2(void *param){
    for(int i=0;i<N;i++){
        lock();
        sum++;
        unlock();
    }
    pthread_exit(0);
}

输出:600000

自旋锁原理与缺陷

原理:一开始桌子上有一把钥匙,每个到来的线程都要拿自己的一个东西与桌子上的东西交换,只有拿到钥匙的人才能进入房间。出房间时将钥匙与桌子上的东西交换。

缺陷:

  1. 性能问题(0)

    • 自旋(共享变量)会触发处理器间的缓存同步,延迟增加
  2. 性能问题(1)

    • 除了进入临界区的线程,其他处理器上的线程都在空转
    • 争抢锁的处理器越多,利用率越低
  3. 性能问题(2)

    • 获得自旋锁的线程可能被操作系统切换出去,实现100%的资源浪费
end

Comments

留言