加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > Linux > 正文

linux 一个写缓存例子

发布时间:2020-12-13 23:12:11 所属栏目:Linux 来源:网络整理
导读:我们已经几次提及 shortprint 驱动; 现在是时候真正看看. 这个模块为并口实现一个非 常简单,面向输出的驱动; 它是足够的,但是,来使能文件打印. 如果你选择来测试这个 驱动,记住你必须传递给打印机一个文件以它理解的格式; 不是所有的打印机在给 一个任意数据

我们已经几次提及 shortprint 驱动; 现在是时候真正看看. 这个模块为并口实现一个非 常简单,面向输出的驱动; 它是足够的,但是,来使能文件打印. 如果你选择来测试这个 驱动,记住你必须传递给打印机一个文件以它理解的格式; 不是所有的打印机在给 一个任意数据的流时很好响应.

?

shortprint 驱动维护一个一页的环形输出缓存. 当一个用户空间进程写数据到这个设备,数据被填入缓存,但是写方法实际没有进行任何 I/O. 相反,shortp_write 的核心看来 如此:

?

while (written < count)

{

/* Hang out until some buffer space is available. */ space = shortp_out_space();

if (space <= 0) {

if (wait_event_interruptible(shortp_out_queue,

(space = shortp_out_space()) > 0))

goto out;

}

?

/* Move data into the buffer. */ if ((space + written) > count)

space = count - written;

?

if (copy_from_user((char *) shortp_out_head,buf,space)) { up(&shortp_out_sem);

return -EFAULT;

}

shortp_incr_out_bp(&shortp_out_head,space); buf += space;

written += space;

?

?

?

?

}

out:


/* If no output is active,make it active. */ spin_lock_irqsave(&shortp_out_lock,flags); if (! shortp_output_active)

shortp_start_output(); spin_unlock_irqrestore(&shortp_out_lock,flags);

?

*f_pos += written;

?

一个旗标 ( shortp_out_sem ) 控制对这个环形缓存的存取; shortp_write 就在上面的 代码片段之前获得这个旗标. 当持有这个旗标,它试图输入数据到这个环形缓存. 函数 shortp_out_space 返回可用的连续空间的数量(因此,没有必要担心缓存回绕); 如果这 个量是 0,驱动等到释放一些空间. 它接着拷贝它能够的数量的数据到缓存中.

?

一旦有数据输出,shortp_write 必须确保数据被写到设备. 数据的写是通过一个工作队 列函数完成的; shortp_write 必须启动这个函数如果它还未在运行. 在获取了一个单独

?

?

?

的,控制存取输出缓存的消费者一侧(包括 shortp_output_active)的数据的自旋锁后,它调用 shortp_start_output 如果需要. 接着只是注意多少数据被写到缓存并且返回.

?

启动输出进程的函数看来如下:

?

static void shortp_start_output(void)

{

if (shortp_output_active) /* Should never happen */ return;

?

/* Set up our ‘missed interrupt‘ timer */ shortp_output_active = 1; shortp_timer.expires = jiffies + TIMEOUT; add_timer(&shortp_timer);

?

/* And get the process going. */ queue_work(shortp_workqueue,&shortp_work);

}

?

处理硬件的事实是,你可以,偶尔,丢失来自设备的中断. 当发生这个,你确实不想你的 驱动一直停止直到系统重启; 这不是一个用户友好的做事方式. 最好是认识到一个中断已 经丢失,收拾残局,继续. 为此,shortprint 甚至一个内核定时器无论何时它输出数据 给设备. 如果时钟超时,我们可能丢失一个中断. 我们很快会看到定时器函数,暂 时,让我们坚持在主输出功能上. 那是在我们的工作队列函数里实现的,它,如同你上面 看到的,在这里被调度. 那个函数的核心看来如下:

?

spin_lock_irqsave(&shortp_out_lock,flags);

/* Have we written everything? */

if (shortp_out_head == shortp_out_tail)

{ /* empty */

shortp_output_active = 0; wake_up_interruptible(&shortp_empty_queue); del_timer(&shortp_timer);

}

/* Nope,write another byte */ else

shortp_do_write();

/* If somebody‘s waiting,maybe wake them up. */

if (((PAGE_SIZE + shortp_out_tail -shortp_out_head) % PAGE_SIZE) > SP_MIN_SPACE)

{

wake_up_interruptible(&shortp_out_queue);

}

spin_unlock_irqrestore(&shortp_out_lock,flags);

?

因为我们在使用共享变量的输出一侧,我们必须获得自旋锁. 接着我们看是否有更多的数 据要发送; 如果无,我们注意输出不再激活,删除定时器,并且唤醒任何在等待队列全空 的进程(这种等待当设备被关闭时结束). 如果,相反,有数据要写,我们调用 shortp_do_write 来实际发送一个字节到硬件.

?

接着,因为我们可能在输出缓存中有空闲空间,我们考虑唤醒任何等待增加更多数据给那 个缓存的进程. 但是我们不是无条件进行唤醒; 相反,我们等到有一个最低数量的空间.

?

每次我们从缓存拿出一个字节就唤醒一个写者是无意义的; 唤醒进程的代价,调度它运行,并且使它重回睡眠,太高了. 相反,我们应当等到进程能够立刻移动相当数量的数据到缓 存. 这个技术在缓存的,中断驱动的驱动中是普通的.

?

为完整起见,这是实际写数据到端口的代码:

?

static void shortp_do_write(void)

{

unsigned char cr = inb(shortp_base + SP_CONTROL);

/* Something happened; reset the timer */ mod_timer(&shortp_timer,jiffies + TIMEOUT);

/* Strobe a byte out to the device */ outb_p(*shortp_out_tail,shortp_base+SP_DATA); shortp_incr_out_bp(&shortp_out_tail,1);

if (shortp_delay)

udelay(shortp_delay);

outb_p(cr | SP_CR_STROBE,shortp_base+SP_CONTROL); if (shortp_delay)

udelay(shortp_delay);

outb_p(cr & ~SP_CR_STROBE,shortp_base+SP_CONTROL);

}

?

这里,我们复位定时器来反映一个事实,我们已经作了一些处理,输送字节到设备,并且 更新了环形缓存指针.

?

工作队列函数没有直接重新提交它自己,因此只有一个单个字节会被写入设备. 在某一处,打印机将,以它的缓慢方式,消耗这个字节并且准备好下一个; 它将接着中断处理器. shortprint 中使用的中断处理是简短的:

?

static irqreturn_t shortp_interrupt(int irq,void *dev_id,struct pt_regs *regs)

{

if (! shortp_output_active) return IRQ_NONE;

/* Remember the time,and farm off the rest to the workqueue function */ do_gettimeofday(&shortp_tv);

queue_work(shortp_workqueue,&shortp_work); return IRQ_HANDLED;

}

?

因为并口不要求一个明显的中断确认,中断处理所有真正需要做的是告知内核来再次运行 工作队列函数.

?

如果中断永远不来如何? 至此我们已见到的驱动代码将简单地停止. 为避免发生这个,我 们设置了一个定时器在几页前. 当定时器超时运行的函数是:

?

static void shortp_timeout(unsigned long unused)

{

unsigned long flags; unsigned char status;

if (! shortp_output_active) return;

?

spin_lock_irqsave(&shortp_out_lock,flags); status = inb(shortp_base + SP_STATUS);

?

/* If the printer is still busy we just reset the timer */ if ((status & SP_SR_BUSY) == 0 || (status & SP_SR_ACK)) {

?

shortp_timer.expires = jiffies + TIMEOUT; add_timer(&shortp_timer); spin_unlock_irqrestore(&shortp_out_lock,flags); return;

}

/* Otherwise we must have dropped an interrupt. */ spin_unlock_irqrestore(&shortp_out_lock,flags); shortp_interrupt(shortp_irq,NULL,NULL);

}

?

如果没有输出要被激活,定时器函数简单地返回. 这避免了定时器重新提交自己,当事情 在被关闭时. 接着,在获得了锁之后,我们查询端口的状态; 如果它声称忙,它完全还没 有时间来中断我们,因此我们复位定时器并且返回. 打印机能够,有时,花很长时间来使 自己准备; 考虑一下缺纸的打印机,而每个人在一个长周末都不在. 在这种情况下,只有 耐心等待直到事情改变.

?

但是,如果打印机声称准备好了,我们一定丢失了它的中断. 这个情况下,我们简单地手 动调用我们的中断处理来使输出处理再动起来.

?

shortpirnt 驱动不支持从端口读数据; 相反,它象 shortint 并且返回中断时间信息. 但是一个中断驱动的读方法的实现可能非常类似我们已经见到的. 从设备来的数据可能被 读入驱动缓存; 它可能被拷贝到用户空间只在缓存中已经累积了相当数量的数据,完整的 读请求已被满足,或者某种超时发生.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读