Linux内核入门到放弃-锁与进程间通信-《深入Linux内核架构》笔记

内核锁机制

对整数的原子操作

<asm-arch/atomic.h>
typedef struct {volatile int counter;} atomic_t;
//初始化只能借助于ATOMIC_INIT宏
atomic_t nmi_active = ATOMIC_INIT(0);

atomic_read(atomic_t *v);
atomic_set(atomic_t *v,int i);
........

自旋锁

自旋锁用于保护短的代码段,其中只包含少量C语句,因此会很快执行完毕。

自旋锁通过spinlock_t数据结构实现,基本上可使用spin_lock和spin_unlock操纵。还有其他一些自旋锁操作:spin_lock_irqsave(spin_unlock_irqsave)不仅获得自旋锁,还停用本地CPU的中断,而spin_lock_bh(spin_unlock_bh)则停用softIRQ。

用法:

spinlock_t lock = SPIN_LOCK_UNLOCKED;
....
spin_lock(&lock);
//临界区
spin_unlock(&lock);

在使用自旋锁时必须注意下面两点:

  • 如果获得锁之后不释放,系统将变得不可用。所有的处理器,迟早需要进入锁对应的临界区。它们会进入无限循环等待锁释放,但等不到。这产生了死锁,从名称来看,这是个应该避免的情况。
  • 自旋锁不应该长期持有,因为所有等待锁释放的处理器都处于不可用状态,无法用于其他工作。

信号量

<asm-arch/semaphore.h>
struct semaphore{
    atomic_t count;
    int sleepers;
    wait_queue_head_t wait;
};

除了down操作之外,还有两种其他的操作用于获取信号量(不同于自旋锁,在退出信号量保护的 临界区时,只有up函数可用)

  • down_interruptible工作方式与down相同,但如果无法获得信号量,则将进程置于TASK_INTERRUPTIBLE状态。因此,在进程睡眠时可以通过信号唤醒。②
  • down_trylock试图获取信号量。如果失败,则进程不会进入睡眠等待信号量,而是继续正常执行。如果获取了信号量,则该函数返回false值,否则返回true

RCU机制

read-copu-update

RCU的性能很好,不过对内存有一定的开销,但大多数情况下可以忽略。这是个好事情,但好事总伴随着一些不那么好的事情。下面是RCU对潜在使用者提出的一些约束。

  • 对共享资源的访问在大部分时间应该是只读的,写访问应该相对很少.
  • 在RCU保护的代码范围内,内核不能进入睡眠状态.
  • 受保护资源必须通过指针访问.

RCU的原理很简单:该机制记录了指向共享数据结构的指针的所有使用者.在该结构将要改变时,则首先创建一个副本,在副本中修改.在所有进行读访问的使用者结束对旧副本的读取之后,指针可以替换为新的,修改后副本的指针.请注意,这种机制允许读写并发进行!

//使用
rcu_read_lock();
p = rcu_dereference(ptr);

if(p != NULL){
    awesome_function(p);
}
rcu_read_unlock();


struct super_duper *new_ptr = kmalloc(...);
new_ptr->meaning = xyz;
new_ptr->of = 42;
new_ptr>life = 23;
//修改
rcu_assign_pointer(ptr,new_ptr);

内存与优化屏障

  • mb()、rmb()、wmb()将硬件内存屏障插入到代码流程中。rmb()是读访问内存屏障。它保证在屏障之后发出的任何读取操作执行之前,屏障之前发出的所有读取操作都已经完成。wmb适用于写访问,语义与rmb类似。读者应该能猜到,mb()合并了二者的语义。
  • barrier插入一个优化屏障。该指令告知编译器,保存在CPU寄存器中、在屏障之前有效的所有内存地址,在屏障之后都将失效。本质上,这意味着编译器在屏障之前发出的读写请求完 成之前,不会处理屏障之后的任何读写请求.
  • read_barrier_depends()是一种特殊形式的读访问屏障,它会考虑读操作之间的依赖性。如果屏障之后的读请求,依赖于屏障之前执行的读请求的数据,那么编译器和硬件都不能重排这些请求
  • smb_mb()、smp_rmb()、smp_wmb()相当于上述的硬件内存屏障,但只用于SMP系统。它们在单处理器系统上产生的是软件屏障。

优化屏障的一个特定应用是内核抢占机制.要注意,preempt_disable对抢占计数器加1因而停用了抢占,preempt_enable通过对抢占计数减1而再次启用抢占.这两个命令之间的代码,可免受抢占的影响.看一看下列代码:

preempt_disable();
function_which_must_node_be_preempted();
preempt_enable();

如果编译器决定将代码重新排序如下,那么就相当麻烦了:

function_which_must_node_be_preempted();
preempt_disable();
preempt_enable();

preempt_disable();
preempt_enable();
function_which_must_node_be_preempted();

为了防止发生上诉的两种情况,不可抢占的部分都变为可抢占.preempt_disable和preempt_enable都加入了内存屏障

<preempt.h>

#define premmpt_disable()\
do{\
    inc_preempt_count();\
    barrier();\
}while(0)

#define premmpt_enable()\
do{\
...
    barrier();\
    preempt_check_resched();\
}while(0)

读者/写者锁

读者/写者自旋锁定义为rwlock_t数据类型。必须根据读写访问,以不同的方法获取锁(read_lock,read_unlock,write_lock,write_unlock).

读/写信号量的用法类似。所用的数据结构是struct rw_semaphore,down_read和up_read用于获取对临界区的读访问。写访问借助于down_write和up_write进

大内核锁

这是内核锁遗迹之一,它可以锁定整个内核,确保没有处理器在核心态并行允许.该锁称为大内核锁(big kernel lock).

BKL的一个特定是,它的锁深度会进行计数.这意味着在内核已经锁定时,仍然可以调用lock_kernel.对应的解锁操作必须调用同样的次数,以解锁内核,使其他处理器能够进入.

尽管BKL在内核中仍然有1 000多处,但它已经是过时的概念,内核开发者废弃了对它的使用.

互斥量

p289

近似的per-CPU计数器

基本思想:计数器的准确值存储在内存中某处,准确值所在内存位置之后是一个数组,每个数组项对应于系统中的一个CPU.

如果一个处理器想要修改计数器的值(加上或减去某个值n),它不会直接修改计数器的值,因为这需要防止其他的CPU访问计数器(这是一个费时的操作)。相反,所需的修改将保存到与计数器相 关的数组中特定于当前CPU的数组项。

如果某个特定于CPU的数组元素修改后的绝对值超出某个阈值,则认为这种修改有问题,将随之 修改计数器的值.

只要计数器改变适度,这种方案中读操作得到的平均值会相当接近于计数器的准确值。

<percpu_counter.h>  
struct percpu_counter {   
    spinlock_t lock;   
    long count;   
    long *counters; 
}; 

<percpu_counter.h>  
#if NR_CPUS >= 16 
#define FBC_BATCH  (NR_CPUS*2) 
#else 
#define FBC_BATCH  (NR_CPUS*4) 
#endif 
 

System V 进程间通信

System V UNIX的3种进程间通信(IPC)机制(信号量,消息队列,共享内存)反映了3种相去甚远的概念,不过三者却有一个共同点.它们都使用了全系统范围的资源,可以由几个进程同时共享.

在各个独立进程都能够访问SysV IPC对象之前,IPC对象必须在系统内唯一表示.为此,每种IPC结构在创建时分配了一个号码.凡知道这个魔数的各个程序,都能够访问对应的结构.如果独立的应用程序需要彼此通信,则通常需要将该魔数永久地编译到程序中.一种备选方案是动态的产生一个保证唯一的魔数(静态分配的号码无法保证唯一).

在访问IPC对象时,系统采用了基于文件访问权限的一个权限系统.

信号量

System V 的信号量接口决不直观,因为信号量的概率已经远超其实际定义了.信号量不再当作是用于支持原子执行预定义操作的简单类型变量.相反,一个System V信号量现在是指一整套信号量,可以允许几个操作同时进行.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define SEMKEY 1234L /*标识符*/
#define PERMS 0666 /*访问权限*/

struct sembuf op_down[1] = {0,-1,0};
struct sembuf op_up[1] = {0,1,0};

int semid = -1;/*信号量ID*/
int res;/*信号量操作的结果*/

void init_sem(){
        /*测试信号量是否已经存在*/
        semid = semget(SEMKEY,0,IPC_CREAT|PERMS);
        if(semid < 0){
                printf("Create the semaphore\n");

                semid = semget(SEMKEY,1,IPC_CREAT|PERMS);
                if(semid < 0){
                        printf("Couldn't create semaphore!\n");
                        exit(-1);
                }
                res = semctl(semid,0,SETVAL,1);
        }
}

void down(){
        res = semop(semid,&op_down[0],1);
}

void up(){
        res = semop(semid,&op_up[0],1);
}

int main(void)
{
        init_sem();
        printf("Before critical code\n");
        down();
        printf("In critical code\n");
        sleep(10);
        up();
        return 0;
}

消息队列

https://blog.csdn.net/Linux_ever/article/details/50346929

共享内存

https://blog.csdn.net/Linux_ever/article/details/50372573

猜你喜欢

转载自www.cnblogs.com/r1ng0/p/10587904.html