《Linux内核深度解析》第5章 内核互斥技术学习及操作
[复制链接]
第5章 内核互斥技术
如果临界区的执行时间比较长或者可能睡眠,可以使用下面这些技术
(1)信号量,大多数情况下我们使用互斥信号量
(2)读写信号量
(3)互斥锁
(4)实时互斥锁
申请这些锁的时候,如果锁被其他进程占有,进程将会睡眠等待,代价很高
如果临界区的执行时间很短,并且不会睡眠,那么使用上面的锁不太合适,因为进程切换的代价很高,可以使用下面互斥技术
(1)原子变量
(2)自旋锁
(3)读写自旋锁,它是对自旋锁的改进,允许多个读者同时进入临界区
(4)顺序锁,它是对自旋锁的改进,读者不会阻塞写者
申请这些锁的时候,如果锁被其他进程占用,进程自旋等待
一原子操作
原子(atomic)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为“不可被中断的一个或一系列操作”,可以保证指令以原子的方式运行,即执行过程不被打断,如果被中断则可能会引起执行结果和预期不符。
atomic_read(atomic_t * v);
该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。
atomic_set(atomic_t * v, int i);
该函数设置原子类型的变量v的值为i。
void atomic_add(int i, atomic_t *v);
该函数给原子类型的变量v增加值i。
atomic_sub(int i, atomic_t *v);
该函数从原子类型的变量v中减去i。
int atomic_sub_and_test(int i, atomic_t *v);
该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。
void atomic_inc(atomic_t *v);
该函数对原子类型变量v原子地增加1。
void atomic_dec(atomic_t *v);
该函数对原子类型的变量v原子地减1。
int atomic_dec_and_test(atomic_t *v);
该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。
int atomic_inc_and_test(atomic_t *v);
该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。
int atomic_add_negative(int i, atomic_t *v);
该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。
int atomic_add_return(int i, atomic_t *v);
该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。
int atomic_sub_return(int i, atomic_t *v);
该函数从原子类型的变量v中减去i,并且返回指向v的指针。
int atomic_inc_return(atomic_t * v);
该函数对原子类型的变量v原子地增加1并且返回指向v的指针。
int atomic_dec_return(atomic_t * v);
该函数对原子类型的变量v原子地减1并且返回指向v的指针。
二自旋锁
原子操作只能对整型变量或者位进行保护,但是在实际的使用环境中不可能只有整形变量或位知名简单的临界区。
自旋锁的自旋就是原地打转的意思,原地打转的目的是等待自旋锁可以用,可以访问共享资源。
缺点:
等待自旋锁的线程会一直处于自旋状态,这样会浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长。因此自旋锁适用于短时间的轻量级加锁。
自旋锁为互斥设备,只有上锁与解锁两种状态
自旋锁示例:
网上找了一个自旋锁的测试例程,来进行实际操作测试下
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>
pthread_spinlock_t spinlock;
int data;
/* 线程工作函数 */
void *thread_work_func(void *dev)
{
while(1)
{ pthread_spin_lock(&spinlock); //上锁
printf("data=%d\n",data);
pthread_spin_unlock(&spinlock); //解锁
sleep(1);
}
}
/* 线程工作函数 */
void *thread_work_func2(void *dev)
{
while(1)
{
pthread_spin_lock(&spinlock); //上锁
data++;
pthread_spin_unlock(&spinlock); //解锁
sleep(1);
}
}
int main(int argc,char **argv)
{
//初始化自旋锁
pthread_spin_init(&spinlock,PTHREAD_PROCESS_PRIVATE);
/*1. 创建子线程1*/
pthread_t thread_id;
if(pthread_create(&thread_id,NULL,thread_work_func,NULL)!=0)
{
printf("子线程1创建失败.\n");
return -1; }
/*2. 创建子线程2*/
pthread_t thread_id2;
if(pthread_create(&thread_id2,NULL,thread_work_func2,NULL)!=0)
{ printf("子线程2创建失败.\n");
return -1;
}
/*3. 等待线程的介绍*/
pthread_join(thread_id,NULL);
pthread_join(thread_id2,NULL);
//销毁自旋锁
pthread_spin_destroy(&spinlock); return 0; }
上面的测试代码分别创建了两个线程,分别访问一个全局变量,采用自旋锁进行保护
实际操作
三信号量
信号量这个概念在ucos等单片机的微型操作系统里其实就已经有学过,算是有些相似吧,但是还是有区别的
信号量的特点:
1信号量可以使等待资源现成进入休眠状态,因此适用于那些占用资源比较久的场合
2信号量不能用于终端中,因为信号量会引起休眠, 中断不能休眠
3如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换现成引起的开销要远大于信号量带来的那点优势
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t sem;
void xprintf(char *str)
{
int i = 0;
while(str[i] != '\0')
{
printf("%c", str[i++]);
fflush(stdout);
sleep(1);
}
return NULL;
}
void *task_fun1(void *arg)
{
sem_wait(&sem);
xprintf((char *)arg);
sem_post(&sem);
return NULL;
}
void *task_fun2(void *arg)
{
sem_wait(&sem);
xprintf((char *)arg);
sem_post(&sem);
return NULL;
}
int main(int argc, char const *argv[])
{
sem_init(&sem, 0, 1);
pthread_t pthid1, pthid2;
pthread_create(&pthid1, NULL, task_fun1, "TASK1");
pthread_create(&pthid2, NULL, task_fun2, "TASK2");
pthread_join(pthid1, NULL);
pthread_join(pthid2, NULL);
sem_destroy(&sem);
return 0;
}
这里用到了多线程,所以要加 -pthread
gcc sem_thread.c –lpthread
测试结果
|