【自旋锁】

article/2025/8/20 15:36:52
1. 原理
     
PV操作原理
记录一个锁定状态(就是一个共享资源,基于原子操作)

2. 适用
1. 解决多cpu之间的竞态
2. 可以解决中断程序和普通程序之间的竞态(自旋锁可以用于中断上下文)
3. 加锁时间不宜过长
4. 获得自旋锁期间,不能进行调度(sleep)
例:
假设AB进程运行于同一CPU
A进程 B进程
获得自旋锁
...
sleep(1);(假设调度到B进程)
请求获得同一自旋锁(永远自旋)
其实就是本CPU死锁
3. 使用方法
#include <linux/spinlock.h>
spinlock_t lock; // 定义
spin_lock_init(&lock); // 初始化
spin_lock(&lock); // 获得自旋锁,才返回 spin_lock_xxx(关闭中断和关闭后半部)
或:
spin_trylock(&lock); // 尝试获得自选锁,获得自旋锁返回真,未获得自旋锁返回假
... // 访问临界资源
spin_unlock(&lock); // 解锁 spin_unlock_xxx(使能/恢复中断和使能/恢复后半部)
【读写自旋锁】  
1. 原理
读锁之间不用锁定,读写锁之间需要锁定
例:
AB进程同时做如下操作:
A进程 B进程
i == 0? i == 0? 不会出问题
i = 0;
(++i) == 0? i == 0 可能会产生问题
ldr r0, [i]
add r0, r0, #1(A认为i 是 1)
i == 0?(B认为i 是 0)
str r0, [i]
i == 0?
2. 适用
读写自旋锁,主要用读锁存在概率较大情况
3. 使用方法
#include <linux/spinlock.h>
rwlock_t lock; // 定义
rwlock_init(&lock); // 初始化
read_lock(&lock); // 读时,获得锁,如果未获得自旋 read_lock_xxx(关闭中断和关闭后半部)
或:
read_trylock(&lock); // 读时,尝试获得自选锁,获得自旋锁返回真,未获得自旋锁返回假
... // 访问临界资源
read_unlock(&lock); // 解锁 read_unlock_xxx(恢复/使能中断和恢复/使能后半部)
write_lock(&lock); // 写时,获得锁,如果未获得自旋 write_lock_xxx(关闭中断和关闭后半部)
或:
write_trylock(&lock); // 写时,尝试获得自选锁,获得自旋锁返回真,未获得自旋锁返回假
... // 访问临界资源
write_unlock(&lock); // 解锁 write_unlock_xxx(恢复/使能中断和恢复/使能后半部)
【顺序锁】
1. 原理
读锁并不锁定写锁,写锁会锁定读锁
2. 适用
1. 主要用读锁存在的概率远大于写锁
2. 顺序锁保护的临界资源不能使指针变量
例:错误用法
A进程 B进程
usngiend seqnum;
do {
seqnum = read_seqbegin(&lock);
切换到B进程
write_seqlock(&lock); 可以获得锁
p = NULL;
切换回A进程
....
*p = 5;(Oops)
} while (read_seqretry(&lock, seqnum))
3. 用法
#include <linux/seqlock.h>
seqlock_t lock; // 定义
seqlock_init(&lock); // 初始化
// 写 获取锁
write_seqlock(&lock); // 可以换成write_seqlock_...
... // 写临界资源
write_sequnlock(&lock); // 可以换成write_sequnlock_...
// 读 不会锁定写,但会被写锁定
unsigned seqnum;
do {
seqnum = read_seqbegin(&lock); // 可以换成read_seqbegin_irqsave(&lock, flags)
... // 读临界资源
} while (read_seqretry(&lock, seqnum)); // 可以换成read_seqretry_restore(&lock, flags)
【信号量】  
1. 原理
信号量其实就是用来表示可以适用的资源数
信号量本身基于自旋锁实现

2. 适用
1. 信号量可以解决多CPU之间的竞态
2. 信号量不能用于中断程序(信号量可能引起休眠,中断不能休眠,中断下休眠系统会奔溃)
3. 信号量持有时间可以较长(还是要尽量的缩短持有信号量时间)
4. 信号量持有期间,可以使用可能引起调度的函数
3. 使用
#include <linux/semaphore.h>
struct semaphore sem; // 定义
sema_init(&sem, val); // val是可用的资源数
down(&sem); // 获取信号量, 获得不到信号量,休眠等待,不返回,一直到获取到信号量时返回
或:
down_interruptable(&sem); // 获取信号量,
// 获得不到信号量,休眠等待, 存在下面两种情况被唤醒:
1. 调用本驱动的进程收到信号,本函数会返回,这时没有获得信号量,所以会返回 1
2. 有进程释放信号量,本函数会获得信号量,同时返回 0
或:
down_trylock(&sem); // 尝试获得信号量
// 获得信号量成功,立即返回 0
// 获得信号量失败,立即返回 1
... // 访问被保护资源
up(&sem)
【读写信号量 】
1. 原理
两个读信号量不会互相锁定,读写信号量之间会互相锁定
2. 适用
读操作使用概率比较大的情况(有多个进程会同时读)
3. 使用方法
[8] 互斥体
只有一个资源的信号量
【阻塞和非阻塞】
[1] 定义
阻塞
读fifo时,如果fifo中没有数据,读进程调用read系统调用时不会返回
非阻塞
读fifo时,如果fifo中没有数据,读进程调用read系统调用时会立即返回,并且返回读到的数据为0
linux进程状态:



检测fifo有无数据的方法:
/*
* @brief 查询文件状态是否改变
* @param[in] @numfds 要检测的文件描述符最大值加1
* @param[in] @readfds 检测文件是否可读的文件描述符集
* @param[in] @writefds 检测文件是否可写的文件描述符集
* @param[in] @exceptfds 检测文件是否发生异常的文件描述符集
* @param[in]] @timeout 超时的结构体定义
* @return > 0 表示发生变化的文件描述符个数
* = 0 等待超时
* -1 表示出错
*/
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
[2] 等待队列
1. 原理
添加到等待队列的进程,会休眠等待唤醒

2. 适用
凡是需要等待休眠的地方都可以用等待队列, 经常用于阻塞进程
3. 使用方法
#include <linux/wait.h>
#include <linux/sched.h>
wait_queue_head_t queue; // 定义等待队列头
init_waitqueue_head(&queue); // 初始化等待队列
// 在需要等待的进程中,调用下面函数:
/*
* 添加进程到等待队列,并且改变进程的状态为深度休眠
* queue 等待队列
* condition 等待条件,为true进程从等待队列中删除,为false继续等待
*/
wait_event(queue, condition);
/*
* 添加进程到等待队列,并且改变进程的状态为浅度休眠
* queue 等待队列
* condition 等待条件,为true进程从等待队列中删除,为false继续等待
* 如果接收到信号,唤醒进程,返回-ERESTARTSYS, 如果是条件成立,唤醒进程,返回0
*/
wait_event_interruptable(queue, condition);
// 另一个进程,唤醒等待进程
// 唤醒等待队列上所有的进程,等待进程会自己检查条件是否成立,成立返回,不成立继续休眠等待
void wake_up(wait_queue_head_t *queue);
wake_up_interruptible(wait_queue_head_t *queue);
[3] 非阻塞实现方法
1. 原理
/************************ poll()函数典型模板************************/
static unsigned int xxx_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;  
struct xxx_dev *dev = filp->private_data; /*获得设备结构体指针*/
...
poll_wait(filp, &dev->r_wait, wait);/* 加读等待队列头 */
poll_wait(filp, &dev->w_wait, wait);/* 加写等待队列头 */
if (...) /* 可读 */
mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/
if (...) /* 可写 */
mask |= POLLOUT | POLLWRNORM; /*标示数据可写入*/
...
return mask;  
图解:

1. 应用程序中调用select查询指定文件是否可读、可写或异常
2. 系统会调用驱动中的poll函数(file_operations中的),查询指定文件是否可读、可写
3. poll返回文件否可读、可写
4. 如果应用程序调用select时,没有加超时,这时系统会调用wait_event_interruptible,
根据select监控的文件描述符集,来确定都等待队列还是写等待队列上等待,读写任何一个条件成立,
当前进程都会被从等待队列上摘除。
如果应用程序调用select时,有加超时, 这时系统会调用wait_event_interruptible_timeout
5. 如果wait_event_timeout超时,系统会再次调用驱动中的poll函数确认指定文件是否可读、可写或异常状态
如果有别的进程调用wake_up_interruptible(如果是读等待队列,这个函数在驱动write函数中调用,
如果是写等待队列,这个函数在驱动read函数中调用),
系统会再次调用驱动中的poll函数确认指定文件是否可读、可写或异常状态
7. select系统调用返回
2. 适用
用于实现非阻塞
3. 使用方法
// wait queque
#include <linux/wait.h>
#include <linux/sched.h>
// poll / select
#include <linux/poll.h>
// 修改read
if (filp->f_flags & O_NONBLOCK) {
// 非阻塞实现
if (!fifo_have_data(&fifo)) {
return -EAGAIN;
}
} else {
// 阻塞
wait_event_interruptible(readq, fifo_have_data(&fifo));
}
// 添加poll函数
/*
* 查询文件状态是否改变
* filp 要查询的文件描述符相对应
* wait 等待队列表
* 返回文件可读、可写、异常状态
*/
unsigned int char_poll(sturct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(filp, &dev->readq, wait);//加读等待队列头,不会进入休眠状态,这个函数会很快返回
if (fifo_have_data(&fifo)) { // 可读?
mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/
}
return mask;
}
// 修改write
写入数据后,要调用wake_up_interruptible

【学习总结】

/****************************************顺序锁(seqlock******************************************************/

/*

 *注意:当要保护的资源很小,很简单,会频繁被访问而且写入访问很少发生且必须快速时,就可以使用seqlock,从本质上讲,seqlock会允许读取者对资源的自由访问,但需要读取者检查是否和写入者发生冲突,当发生这种冲突,就需要重试对资源的访问,seqlock通常不能保护包含指针的数据结构

 */

#include <linux/seqlock.h>

seqlock_t lock=SEQLOCK_UNLOCKED;

seqlock_t lock;

seqlock_init(&lock);

//读存取通过在进入临界区入口获取一个(无符号的)整数序列来工作在退出时,那个序列值与当前值比较如果不匹配读存取必须重试读者代码如下面的形式:

unsigned int seq;

do {

seq = read_seqbegin(&the_lock);

/* Do what you need to do */

} while read_seqretry(&the_lock, seq);

//这个类型的锁常常用在保护某种简单计算需要多个一致的值如果这个计算最后的测试表明发生了一个并发的写结果被简单地丢弃并且重新计算.如果你的 seqlock 可能从一个中断处理里存取你应当使用 IRQ 安全的版本来代替:

unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned longflags);

int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsignedlong flags);

//写者必须获取一个锁来进入由一个seqlock 保护的临界区为此调用:

void write_seqlock(seqlock_t *lock);

//写锁由一个自旋锁实现因此所有的通常的限制都适用调用:

void write_sequnlock(seqlock_t *lock);

//来释放锁因为自旋锁用来控制写存取所有通常的变体都可用:

void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);

void write_seqlock_irq(seqlock_t *lock);

void write_seqlock_bh(seqlock_t *lock);

void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);

void write_sequnlock_irq(seqlock_t *lock);

void write_sequnlock_bh(seqlock_t *lock);

//还有一个 write_tryseqlock 在它能够获得锁时返回非零.

/****************************end******************************************/

 

/***************************ioctl******************************************/

//在用户空间, ioctl 系统调用有下面的原型:

int ioctl(int fd, unsigned long cmd, ...);

//ioctl 驱动方法有和用户空间版本不同的原型:

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd,unsigned long arg);

inodefilp两个指针的值对应于应用程序传递的文件描述符fd,参数cmd由用户空间不经修改地传递给驱动程序

。。。。。。

/****************************end*********************************************/

 

 

/****************************休眠*********************************************/

/*

当一个进程被置为休眠时,它会被标记为一种特殊状态并从调度器的运行队列移走,直到某些情况下修改了这个状态,进程才会在任CPU调度,也即运行该进程,休眠中的进程会被搁置在一边,等待将来的每个事件发生,我们的驱动程序不能再拥有自旋锁锁时休眠、seqlock或者RCU锁时休眠,如果我们已经禁止了中断也不能休眠,拥有信号量休眠是合法的,但任何拥有信号量

而休眠的代码必须很短,并且还要确保有能唤醒自己,能够找到休眠的进程意味着需要维护一个称为等待队列的数据结构,等待队列就是一个进程链表,其中包含了等待某个特定事件的所有进程,在linux中,一个等待队列通过一个等待队列头来管理

*/

//等待队列:

#include <linux/wait.h>

//初始化一个等待队列头:

DECLARE_WAIT_QUEUE_HEAD(name);

wait_queue_head_t xxx_queue; //定义一个等待队列头

init_waitqueue_head(&xxx_queue);//初始化对待队列头

DECLARE_WAITQUEUE(name, tsk); //定义等待队列,wait_queue_t namestruct task_struct *tsk

//添加或移除等待队列:

void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait );

//将等待队列wait添加到等待队列头q指向的等待队列链表中

void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait );

//将等待队列wait从等待队列头q指向的等待队列链表中移除

//等待事件(简单休眠):

wait_event(xxx_queue,condition);

 //xxx_queue为等待队列头,是值传递,condition是任意一个布尔表达式,在条件为真前,进程会保持休眠

wait_event_interruptible(xxx_queue,condition);

//可中断,返回值为非0表示休眠被某个信号中断,而驱动程序要返回-ERESTARTSYS

wait_event_timeout(xxx_queue,condition,timeout);

//只会等待给定的时间,当给定的时间到期时,这两个宏都会返回0值,不论condition如何求值,如果由其他事件唤醒,则返回剩余的延时时间

wait_event_interruptible_timeout(xxx_queue,condition,timeout);

//唤醒休眠的进程:

void wake_up(wait_queue_head_t *queue);

void wake_up_interruptible(wait_queue_head_t *queue);

//在等待队列上睡眠:

sleep_on(wait_queue_head_t *q);

//将目前进程的状态设置为TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头,直到资源可获取,q引导的等待队列被唤醒,其和wake_up成对使用

interruptible_sleep_on(wait_queue_head_t *q);

//与上类似,其将目前进程的状态设置为TASK_INTERRUPTIBLE,其和wake_up_interruptible成对使用

/*

高级休眠:

复杂的锁定以及性能需求会强制驱动程序使用底层的函数来实现休眠

将进程置于休眠的步骤:

1 分配并初始化一个等待队列wait_queue_t,然后将其加入到对应的等待队列头指向的等待链表

2 设置进程的状态,将其标记休眠,<linux/sched.h>中定义了多个任务状态,TASK_RUNNING表示进程可运行,尽管进程并不一定在任何给定时间都运行在每个处理器上,TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE表明进程处于休眠状态,可调用下面函数来设置进程状态:

      void set_current_state(int new_state); //在老代码中还可以看到current->state=TASK_INTERRUPTIBLE

3)通过改变当前进程状态,我们只是改变了调度器处理该进程的方式,但尚未使进程让出处理器,那么最后一步就是放弃处理器

调用下面函数:

     schedule(); //schedule的调用将调用调度器,并让出CPU

*/

//手工休眠:

1)建立并初始化一个等待队列入口

     DEFINE_WAIT(xxx_wait);//name为等待队列入口变量的名称

     

     wait_queue_t xxx_wait;

     init_wait(&xxx_wait);

2)将等待队列入口添加到队列中,并设置进程的状态

    void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state); //queuewait分别是等待队列头和进程入口,state为进程的新状态,它应该是TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE

3)让出CPU

    if(条件没有就绪)

      schedule();

4)一旦 schedule返回,就到了清理时间了

     void finish_wait(wait_queue_head_t *queue,wait_queue_t *wait) ;

5)之后,代码可测试其状态,并判断是否需要重新等待

//在许多设备驱动中亲自进行进程的状态改变和切换:

 {

  DECLARE_WAITQUEUE(wait, current); //定义等待队列

  add_wait_queue(&xxx_wait,&wait ); //添加等待队列

 

  do{

   .....

    if(资源未就绪)

     {

      __set_current_state(TASK_INTERRUPTIBLE); //改变进程状态

      schedule();//调度其他进程执行

      }

    .....

    }while(资源未就绪);

  

  ...........

  ...........

 }

/*********************************end**********************************************/

 

/********************************poll机制(非阻塞)************************************/

原型:unsigned int xxx_poll(struct file filp, poll_table *wait);

// poll_table结构用于实现pollselect系统调用

 

#include <linux/poll.h>

1void poll_wait(struct file *filp,wait_queue_head_t queue, poll_table *wait);

//poll_table结构添加一个等待队列,不会休眠,而是会在系统调用知道超时时间时,超时才会休眠

2poll的第二项任务是返回描述哪个操作可以立即执行的位掩码

    常用的位掩码有:

                    POLLLIN //如果设备可以无阻塞的读取,就设置该位

                  

                    POLLRDNORM

//如果通常的数据已经就绪,可以读取,就设置该位一个可读设备返回(POLLLIN|POLLRDNORM)

                  

                    POLLHUP

 // 当读取设备的进程到达文件尾时,驱动程序必须设置该(挂起)位,依照select功能描述,调用select的进程会被告知设备是可读的

                  

                    POLLERR //设备发生了错误

                  

                    POLLOUT //如果设备可以无阻塞的写入,就设置该位

                  

                    POLLWRNORM

//如果通常的数据已经就绪,可以写入,就设置该位一个可读设备返回(POLLOUT|POLLWRNORM)

一般程序设计模板:

             unsigned int xxx_poll(struct file filp, poll_table *wait)

                {

                 unsigned int mask;

                 wait_poll(filp,&xxx_dev->inq,wait);

                 wait_poll(filp,&xxx_dev->outq,wait);

                 if(可读)

                   mask=POLLLIN|POLLRDNORM;

                 if(可写)

                   mask=POLLOUT|POLLWRNORM;

                 if(无数据可获取)

                   mask=POLLHUP;

                 return mask;

                }

 

/**********************************end*****************************************/

【学习代码】
1. 自旋锁spinlock的实现

【主程序1char_dev.c

// 模块头文件

#include <linux/init.h>

#include <linux/module.h>

// 字符设备头文件

#include <linux/cdev.h>

#include <linux/fs.h>

// copy_to_user/copy_from_user

#include <linux/uaccess.h>

// ioctl

#include <linux/ioctl.h>

// 导出设备信息到sysfs

#include <linux/device.h>

#include "char_dev.h"

#include "fifo.h"

// 1. 创建字符设备对象(定义结构体变量)

// struct cdev char_demo; 静态定义

struct cdev *char_demo;

struct class * char_demo_class;

struct device *char_demo_device;

FIFO fifo; // 1. 定义

// 0-表示需用动态分配设备号 0 - 表示静态分配设备号

int major = 0;

int minor = 0;

int char_open(struct inode *inode, struct file *filp)

{

 printk("char open\n");

 return 0;

}

// int read(fd, buf, sizeof(buf)); app

ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 // 3. 读功能实现

 ret = fifo_read(&fifo, fifo_buf, sizeof(fifo_buf));

 if (ret < 0) {

  ret = 0;

  goto exit;

     }

 

 /*

  * @brief 拷贝数据到应用程序空间

  * @param[out] to 应用程序空间buf,要拷贝到的地方

  * @param[in] from 内核空间buf,从哪个地方拷贝数据

  * @param[in] n 拷贝数据长度

  * @return 未成功拷贝的数据数量一般返回0

  * @notes copy_to_user可能会引起睡眠

  */

 // unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

 

 ret = copy_to_user(buf, fifo_buf, 50);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = 50;

 }

 

exit:

 return ret;

}

// int write(fd, buf, len);

ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 if (size > 50) {

  size = 50;

 }

 

 /*

  * @brief 拷贝数据从应用程序空间

  * @param[out] to 内核空间buf,要拷贝到的地方

  * @param[in] from 应用空间buf,从哪个地方拷贝数据

  * @param[in] n 拷贝数据长度

  * @return 未成功拷贝的数据数量一般返回0

  * @copy_from_user可能会引起睡眠

  */

 // unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

 

 ret = copy_from_user(fifo_buf, buf, size);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = size;

  printk("%s\n", fifo_buf);

 }

 // 4. 写功能实现

 fifo_write(&fifo, fifo_buf);

 

 return ret;

}

long char_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

{

 long ret = 0;

 

 switch (cmd) {

  case CMD_DEMO0:

   printk("CMD_DEMO0\n");

   break;

  

  case CMD_DEMO1:

   printk("CMD_DEMO1\n");

   break;

  

  default:

   ret = -ENOTTY;

   break;

 }

 

 return ret;

}

int char_release(struct inode *inode, struct file *filp)

{

 printk("char release\n");

 return 0;

}

struct file_operations fops = {

 .owner = THIS_MODULE, // 当前模块

 .open = char_open,

 .read = char_read,

 .write = char_write,

 .unlocked_ioctl = char_ioctl,

 .release = char_release,

};

 

int __init char_init(void)

{

 int ret = 0;

 dev_t devno;

 

 // 给设备分配位置

 // 设备号申请成功(cat /proc/devices 会有主设备号和名字的对应关系)

 if (major != 0) {

  // 静态分配设备号

  // 找设备号的方法:Documention/devices

  devno = MKDEV(major, 0);

  ret = register_chrdev_region(devno, 1, "char demo");

  if (ret) {

   goto register_chrdev_region_err;

  }

 

 } else {

 

  /*

  * @brief 动态分配设备号

  * @param[out] dev 返回分配的设备号

  * @param[in] baseminor 第一个次设备号

  * @param[in] count 分配设备号的数量

  * @param[in] name 主设备号的名字

  * @return 0 分配成功

  * < 0 错误码

  *

  int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,

   const char *name)

  */

   ret = alloc_chrdev_region(&devno, minor, 1, "char demo");

   if (ret) {

    goto register_chrdev_region_err;

   }

   major = MAJOR(devno);

 }

 

 // 1. 创建字符设备对象(动态分配)

 char_demo = cdev_alloc();

 if (NULL == char_demo) {

  ret = -ENOMEM;

  goto cdev_alloc_err;

 }

 

 // 2. 初始化字符设备对象

 char_demo->owner = THIS_MODULE;

 char_demo->ops = &fops;

 // cdev_init(&char_demo, &fops);

 

 // 2. fifo初始化

 fifo_init(&fifo);

 

 // 3. 添加字符设备到内核

 // ret = cdev_add(&char_demo, devno, 1);

 ret = cdev_add(char_demo, devno, 1);

 if (ret) {

  goto cdev_add_err;

 }

 

 // 自动创建设备文件结点(udevfs/mdev)

 // 1. 创建设备类

 char_demo_class = class_create(THIS_MODULE, "char_demo");

 if (IS_ERR(char_demo_class)) {

  ret = PTR_ERR(char_demo_class);

  goto class_create_err;

 }

 

 // 2. 导出设备信息到应用空间(类的目录下)

 char_demo_device = device_create(char_demo_class, NULL, devno, NULL, "chardev");

 if (IS_ERR(char_demo_device)) {

  ret = PTR_ERR(char_demo_class);

  goto device_create_err;

 }

 

 goto register_chrdev_region_err;

 

device_create_err:

 class_destroy(char_demo_class);

 

class_create_err:

 cdev_del(char_demo);

cdev_add_err:

cdev_alloc_err:

 unregister_chrdev_region(devno, 1);

 

register_chrdev_region_err:

 return ret;

}

void __exit char_exit(void)

{

 dev_t devno = MKDEV(major, minor);

 

 device_destroy(char_demo_class, devno);

 class_destroy(char_demo_class);

 // cdev_del(&char_demo);

 cdev_del(char_demo);

 unregister_chrdev_region(devno, 1);

}

module_init(char_init);

module_exit(char_exit);

MODULE_LICENSE("GPL");

 

【主程序2fifo.c

#include "fifo.h"

void fifo_init(FIFO *fifo)

{

 memset(fifo->buf, 0, sizeof(fifo->buf));

 fifo->read = 0;

 fifo->write = 0;

 

 // 2. 自旋锁初始化

 spin_lock_init(&fifo->lock);

}

int fifo_read(FIFO *fifo, char *buf, int size)

{

 if (NULL == fifo) {

  return -1;

 }

 if (size > ENUM_SIZE) {

  return -1;

 }

 

 spin_lock(&fifo->lock);

 if (fifo->read == fifo->write) {

  spin_unlock(&fifo->lock);

  return -1;

 }

 spin_unlock(&fifo->lock);

 

 memcpy(buf, fifo->buf[fifo->read], ENUM_SIZE);

 

 spin_lock(&fifo->lock);

 fifo->read += 1;

 fifo->read %= BUF_NUM;

 spin_unlock(&fifo->lock);

 

 return 0;

}

int fifo_write(FIFO *fifo, char *buf)

{

 if (NULL == fifo) {

  return -1;

 }

 

 if (NULL == buf) {

  return -1;

 }

 

 spin_lock(&fifo->lock);

 if ((fifo->write + 1) % ENUM_SIZE == fifo->read) {

  spin_unlock(&fifo->lock);

  return -1;

 }

 spin_unlock(&fifo->lock);

 

 memcpy(fifo->buf[fifo->write], buf, ENUM_SIZE);

 

 spin_lock(&fifo->lock);

 fifo->write += 1;

 fifo->write %= BUF_NUM;

 spin_unlock(&fifo->lock);

 

 return 0;

}

【头文件1char_dev.h

#ifndef __CHAR_DEV_H__

#define __CHAR_DEV_H__

#define CMD_DEMO0  _IO('K', 0)

#define CMD_DEMO1  _IO('K', 1)

#endif // __CHAR_DEV_H__

 

【头文件2fifo.h

#ifndef __FIFO_H__

#define __FIFO_H__

#include <linux/spinlock.h>

#include <linux/string.h>

#define BUF_NUM  5

#define ENUM_SIZE  50

typedef struct fifo_t{

 char buf[BUF_NUM][ENUM_SIZE];

 int read, write;

 

 // 自旋锁使用 1. 定义

 spinlock_t lock;

} FIFO;

void fifo_init(FIFO *fifo);

int fifo_read(FIFO *fifo, char *buf, int size);

int fifo_write(FIFO *fifo, char *buf);

#endif // __FIFO_H__

 

Makefile

# KERNELRELEASE 变量装载的是内核版本,变量在内核顶层目录下的Makefile赋值

ifeq ($(KERNELRELEASE),)

内核源代码目录(模块将运行的内核的)

#KERNELDIR ?= /usr/src/linux-headers-$(shell uname -r) #(uname -r查看)

KERNELDIR ?= /work/S5PC100/linux-2.6.35-farsight

PWD := $(shell pwd)

调用内核顶层目录下的Makefile,是它调用本Makefile编译本模块

# -C 在做任何事情之前,进入指定目录

# M= 告诉内核顶层目录的Makefile,模块的位置

# modules 告诉内核顶层目录的Makefile 仅仅编译模块

all:

 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

清除编译出来的文件

clean:

rm -rf *.*~ core .depend .*.cmd *.ko *.mod..tmp_versions Module* modules*

else

被内核顶层目录的Makefile调用,来编译模块

obj-+= char_fifo.o #模块重命名

char_fifo-objs := char_dev.o fifo.o

endif

 

2. 阻塞wait_queue的实现

相对于上面的自旋锁,只需要修改主程序1中的几个函数:

// int read(fd, buf, sizeof(buf)); app

ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 wait_event_interruptible(readq, fifo_have_data(&fifo));

 

 // 3. 读功能实现

 ret = fifo_read(&fifo, fifo_buf, sizeof(fifo_buf));

 if (ret < 0) {

  ret = 0;

  goto exit;

     }

 

 ret = copy_to_user(buf, fifo_buf, 50);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = 50;

     }

 

exit:

     return ret;

}

ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 if (size > 50) {

  size = 50;

 }

 

 ret = copy_from_user(fifo_buf, buf, size);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = size;

  printk("%s\n", fifo_buf);

 }

 // 4. 写功能实现

 fifo_write(&fifo, fifo_buf);

 

 // 4. 另一进程唤醒

 wake_up_interruptible(&readq);

 

 return ret;

}


3. 非阻塞poll的实现

相对于上面的阻塞,只需要修改主程序1中的几个函数:

int char_open(struct inode *inode, struct file *filp)

{

 if (filp->f_flags & O_NONBLOCK) {

  printk("no block\n");

 }

 

 printk("char open\n");

 return 0;

}

ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 // 5. 实现读非阻塞

 if (filp->f_flags & O_NONBLOCK) {

  // 非阻塞实现

  if (!fifo_have_data(&fifo)) {

   return -EAGAIN;

  }

 } else {

  wait_event_interruptible(readq, fifo_have_data(&fifo));

 }

 

 ret = fifo_read(&fifo, fifo_buf, sizeof(fifo_buf));

 if (ret < 0) {

  ret = 0;

  goto exit;

 }

 

 ret = copy_to_user(buf, fifo_buf, 50);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = 50;

 }

 

exit:

 return ret;

}

ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 if (size > 50) {

  size = 50;

 }

 

 ret = copy_from_user(fifo_buf, buf, size);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = size;

  printk("%s\n", fifo_buf);

 }

 fifo_write(&fifo, fifo_buf);

 

 // 4. 另一进程唤醒

 wake_up_interruptible(&readq);

 

 return ret;

}


http://chatgpt.dhexx.cn/article/bDXni537.shtml

相关文章

量子力学之电子自旋与四个量子数

量子力学之电子自旋与四个量子数 前言一、电子自旋是什么&#xff1f;二、四个量子数1.主量子数 n2.角量子数*l*3.磁量子数ml4.自旋量子数ms 三.例题 前言 在笔者学习大学物理量子力学部分时&#xff0c;对此部分非常疑惑&#xff0c;弄明白之后写下来以供查看&#xff0c;水平…

学习自旋电子学的笔记03:初试自旋波模拟

文章目录 前言一、初遇1.Figure S2 (a)2.图4-23.Figure S1 二、暂别1.FFT分析程序包&#xff1a;MFA简介2.使用练习MFA 三、重逢3.Figure S14.FIG.2 (a)5.FIG.2 (b)6.FIG.5 总结 _ _ 远行&#xff01; 前言 四月&#xff0c;过得四真的快啊&#xff0c;这是从入学到现在的第9个…

深入理解CAS (自旋锁)

文章目录 0. 导言1. 什么是CAS2. 保证原子操作2.1 CAS 实现自旋锁2.2 AtomicBoolean 中的CAS2.3 CAS使用场景 3. 锁的分类3.1 乐观锁3.2 悲观锁 4. CAS存在的问题4.1 ABA问题4.2 循环时间长开销大4.3 只能保证一个共享变量的原子操作 0. 导言 背景&#xff1a; 我们都知道&…

CAS和自旋锁

什么是CAS CAS算法&#xff08;Compare And Swap&#xff09;&#xff0c;即比较并替换&#xff0c;是一种实现并发编程时常用到的算法&#xff0c;Java并发包中的很多类都使用了CAS算法。 CAS算法有3个基本操作数&#xff1a; 内存地址V旧的预期值A要修改的新值B CAS使用自…

Java中的自旋锁,手动实现一个自旋锁

自旋锁 CAS是实现自旋锁的基础,CAS利用CPU指令保证了操作的原子性,已达到锁的效果。自旋是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁, 当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环…

学习自旋电子学的笔记04:模拟自旋波在弯曲磁畴壁中传播

文章目录 前言零、笔记03中错误的补充改正1.保持电子的极化方向不变的原因2.Oxs_SpinXferEvolve类的额外补充说明3.时间演化器的时间步长相关补充说明 一、文章概述和要复现的微磁模拟1.文章概述2.要复现的微磁模拟 二、FIG.1三、 FIG.21. FIG.2(a-b)2. FIG.2(c-f) 四、 FIG.3五…

CAS和自旋到底是一个概念吗?

问题: CAS是 compare and swap ,就是一个比较工作内存和主内存的值是否相同&#xff0c;相同的话&#xff0c;就用新值来替换这么一个操作。 但是&#xff0c;为什么好多地方都说这是自旋呢&#xff1f; 我理解比较一次的话&#xff0c;成功就返回true了&#xff0c;失败&am…

CAS及CAS自旋

1. CAS简介 比较并交换(compare and swap, CAS)&#xff0c;是原子操作的一种。在多线程没有锁的状态下&#xff0c;可以保证多个线程对同一个值的更新。 CAS可用于在多线程编程中实现不被打断的数据交换操作&#xff0c;从而避免多线程同时改写某一数据时由于执行顺序不确定…

自旋玻璃(spin glass)、自旋冰(spin ice)和量子自旋液体(quantum spin liquid)(之二)

文章目录 13. 几何阻挫&#xff08;Geometrical frustration&#xff09;13.1 磁序&#xff08;Magnetic ordering&#xff09;13.2 数学定义13.3 水冰&#xff08;water ice&#xff09;13.4 Paulings model 的扩展&#xff1a;广义的阻挫13.5 人工几何阻挫铁磁体13.6 没有晶格…

CAS自旋

文章目录 1. CAS简介2. CAS的特点3. 自旋–比较和交换4. 什么是ABA问题5. ABA问题怎么解决6. 悲观锁7. 乐观锁8. CAS锁升级 CAS面试提问环节&#xff1a; synchronized、ReentrantLock、CAS全家桶发售HashMap、Hashtable、ConcurrentHashMap组合拳出击 1. CAS简介 比较并交换(…

自旋玻璃(spin glass)、自旋冰(spin ice)和量子自旋液体(quantum spin liquid)(之一)

文章目录 1. Giorgio Parisi 简介2. 复杂无序系统2.1 相变、序参量与对称性破缺2.2 复杂系统 3. 自旋玻璃简介3.1 自旋冻结3.2 亚稳态3.3 磁化弛豫3.4 玻璃化和无序系统3.5 Ising model3.6 自旋玻璃模型3.7 自旋玻璃相变 4. 磁场中的现象5. Edwards-Anderson model6. Sherringt…

office卸载工具怎么用(官方干净卸载方法)

https://jingyan.baidu.com/article/39810a23593f37b636fda60d.html

Office卸载安装问题

卸载Office 问题描述 此前已安装过新的Office&#xff0c;按照正常的卸载流程卸载后&#xff08;控制面板卸载后&#xff09;&#xff0c;安装新的Office时&#xff0c;提示&#xff1a; 无法安装64位版本的Office&#xff0c;因为在电脑上已有32位程序。如下图所示。 **解…

32位office卸载不干净怎么办如何删除32位Office

以前在电脑上安装了32位系统的Office&#xff0c;现在想要换成64位的Office&#xff0c;但是在安装的时候提示无法进行安装&#xff0c;需要先卸载以前的32位Office&#xff0c;出现这种情况怎么办呢&#xff1f;如何彻底卸载干净32位系统的Office呢&#xff1f;下面就一起来看…

Office卸载不干净,注册表项权限修改后仍然无法删除的问题

Office卸载不干净&#xff0c;注册表项权限修改后仍然无法删除的问题 针对卸载Office最极端的情况&#xff0c;试试以下方法。 1.卸载开始菜单的office; 可以借助以下工具进行进行清除&#xff0c;完全卸载&#xff08;但是对本人无效&#xff09; 链接&#xff1a;https://…

mac m1 office卸载重装(学校官方正版)

卸载office 1、应用程序中将所有的microsoft相关软件移到废纸篓&#xff0c;可能还有outlook或者onedrive&#xff0c;清空废纸篓 2、cmdshiftG&#xff0c;输入/Library/Preferences&#xff0c;删除所有com.Microsoft开头的文件 3、进入/Library/PrivilegedHelperTools&…

卸载32位office安装64位office卸载不完全导致不能安装64位office时解决办法

转载自https://blog.csdn.net/zzfenglin/article/details/60780831 问题描述 安装64位office办公软件的时候提示已经安装32位的office办公软件所以无法继续安装&#xff0c;但实际上之前安装的32位的office办公软件已经卸载了。问题现象截图如下&#xff1a; 解决办法 从问题描…

office2019 完美卸载

记录下我日常手贱的经历。 事情的起因呢&#xff0c;是在家办公的时候呢&#xff0c;突然要写一份文档&#xff0c;然后呢&#xff0c;我的wps过期了&#xff0c;只能看&#xff0c;而不能编辑。然后我就打算装个office2019感受下。结果我就卸载了原来安装过但是已经过期了的o…

Office uninstall(专业office卸载工具)绿色单文件版V1.8.3 全面清除office卸载残留

Office uninstall 是一款专门为微软Office办公软件量身定做的office卸载工具&#xff0c;可以帮助大家彻底卸载已经安装到电脑上的Office软件&#xff0c;彻底解决office卸载不干净&#xff0c;无法重新安装的问题&#xff0c;全面兼容office2003、office2007、office2010、off…

Win10预装Office卸载工具

联想软件远程服务&#xff0c;让您足不出户&#xff0c;轻松解决电脑问题&#xff01;软件调试、电脑加速、游戏加速、重装系统、修复浏览器、安装驱动&#xff0c;远程or上门&#xff1f;联想专家一对一服务任您挑选&#xff01; 重要提示&#xff1a;您需要在电脑端下载并运行…