驱动程序与操作系统概览
设备与驱动关系
中断处理流程: 键盘事件
设备与驱动概念
设备
举个例子,储存设备,其实不管它是机械硬盘,还是 TF 卡,或者是一个设备控制代码模块,它向操作系统内核表明它是储存设备,但它完全有可能分配一块内存空间来储存数据,不必访问真正的储存设备
所以,操作系统内核所感知的设备,并不需要和物理设备对应,这取决于设备控制代码自身的行为
驱动
操作系统内核和应用程序都不会主动建立设备,那么谁来建立设备呢?当然是控制设备的代码,也就是我们常说的驱动程序
案例: 自定义命令行终端
应用端代码
现在以一个自定义命令行终端,揭示驱动与操作系统之间的关系
/**
* @brief 自定义命令终端
* 1. 创建句柄
* 2. 通过句柄读取信息
* 3. 通过句柄写入信息
*/
void shell()
{
hand_t hand = -1;
char_t charbuff[2] = {0,0};
u16_t kbcode;
hand = open_keyboard();
memset((void*)&shellkbuff, 0, sizeof(shellkbbuff_t));
printf("Cosmos shell is init!\n");
printf("Cosmos@IVANQI:>");
for(;;) {
kbcode = read_keyboard_code(hand);
if (kbcode == 0) return;
if (32 <= kbcode && 127 >= kbcode) {
charbuff[0] = (char_t)kbcode;
skb_buff_write(&shellkbuff, charbuff[0]);
skb_buff_disp(&shellkbuff);
continue;
}
if (0x103 == kbcode) {
skb_buff_enter(&shellkbuff);
skb_buff_clear(&shellkbuff);
continue;
}
if (0x104 == kbcode) {
skb_buff_backspace(&shellkbuff);
continue;
}
}
close_keyboard(hand);
return;
}
设备驱动预先初始化
设备初始化
void init_krldevice()
{
// 初始化系统全局设备表
devtable_t_init(&osdevtable);
return;
}
/**
* @brief 初始化设备
* 1. 初始化对应的设备分类
*
* @param initp
*/
void devtable_t_init(devtable_t *initp)
{
list_init(&initp->devt_list);
krlspinlock_init(&initp->devt_lock);
list_init(&initp->devt_devlist);
list_init(&initp->devt_drvlist);
initp->devt_devnr = 0;
initp->devt_drvnr = 0;
// 初始化设备链表。 DEVICE_MAX 为设备分类上限
for (uint_t t = 0; t < DEVICE_MAX; t++) {
devtlst_t_init(&initp->devt_devclsl[t], t);
}
return;
}
初始化所有设备,并赋值到devtable_t,进行设备按类别管理,同时
devtlst_t_init(&initp->devt_devclsl[t], t);
初始化不同类别下的设备资源
最后保存在osdevtable全局变量中,待后续应用端代码使用
驱动初始化
// 驱动程序表,然后再init_krldriver中运行
KRL_DEFGLOB_VARIABLE(drventyexit_t, osdrvetytabl)[] = {
systick_entry, // 定时器驱动
rfs_entry, // 文件驱动
uart_entry, // 键盘驱动
NULL
};
/**
* @brief 驱动程序的初始化
* 1. 根据驱动程序表初始化程序入口
*/
void init_krldriver()
{
// 遍历驱动程序表中的每个驱动程序入口函数
for (uint_t ei = 0; osdrvetytabl[ei] != NULL; ei++) {
// 运行一个驱动程序入口
if (krlrun_driverentry(osdrvetytabl[ei]) == DFCERRSTUS) {
hal_sysdie("init driver err");
}
}
kprint("设备驱动初始化成功\n");
return;
}
对键盘驱动进行驱动初始化
/**
* @brief 驱动程序入口函数
* 1. 建立driver_t实例变量
* 2. 运行驱动程序入口函数
* 3. 把驱动程序加入系统
* 4. 把驱动加入挂载到devtable_t
*
* @param drventry
* @return drvstus_t
*/
drvstus_t krlrun_driverentry(drventyexit_t drventry)
{
driver_t *drvp = new_driver_dsc(); // 建立driver_t实例变量
if (drvp == NULL) {
return DFCERRSTUS;
}
if (drventry(drvp, 0, NULL) == DFCERRSTUS) { // 运行驱动程序入口函数
return DFCERRSTUS;
}
// 把驱动加入挂载到devtable_t
if (krldriver_add_system(drvp) == DFCERRSTUS) { // 把驱动程序加入系统
return DFCERRSTUS;
}
return DFCOKSTUS;
}
比如初始化的是键盘驱动,就会跳转键盘驱动的入口处理函数中
/**
* @brief 键盘驱动程序入口
* 1. 新建设备
* 2. 设置驱动默认函数
* 3. 把设备加入到驱动的设备链表中
* 4. 安装中断回调函数接口.用于后续的中断处理
*
* @param drvp 驱动结构体
* @param val
* @param p
* @return drvstus_t
*/
drvstus_t uart_entry(driver_t *drvp, uint_t val, void *p)
{
if (drvp == NULL) {
return DFCERRSTUS;
}
device_t *devp = new_device_dsc();
if (devp == NULL) {
return DFCERRSTUS;
}
// 省略以下设备和驱动的初始化信息
// 把设备加入到驱动的设备链表中
if (krldev_add_driver(devp, drvp) == DFCERRSTUS) {
if (del_device_dsc(devp) == DFCERRSTUS) { //注意释放资源。
return DFCERRSTUS;
}
return DFCERRSTUS;
}
// 向内核(设备表)注册设备
if (krlnew_device(devp) == DFCERRSTUS) {
if (del_device_dsc(devp) == DFCERRSTUS) { //注意释放资源
return DFCERRSTUS;
}
return DFCERRSTUS;
}
// 安装中断回调函数接口
if (krlnew_devhandle(devp, uart_handle, 0x21) == DFCERRSTUS) {
return DFCERRSTUS; // 注意释放资源。
}
...
return DFCOKSTUS;
}
最终设备和驱动数据结构简陋示例图
中断处理流程: 键盘事件
中断事件注册
初始化段描述符和TSS描述符
/**
* @brief 初始化段描述符和TSS描述符
*
* @return void
*/
PUBLIC LKINIT void init_descriptor()
{
set_idt_desc(INT_VECTOR_IRQ0 + 1, DA_386IGate, hxi_hwint01, PRIVILEGE_KRNL);
}
汇编定义
; 硬件1~7号中断. 定义了各种硬件中断编号
hxi_hwint01:
HARWINT (INT_VECTOR_IRQ0+1)
; 硬件中断,硬件分发器函数 hal_hwint_allocator
%macro HARWINT 1
SAVEALL
mov r14w, 0x10
mov ds, r14w
mov es, r14w
mov fs, r14w
mov gs, r14w
mov rdi, %1
mov rsi, rsp
call hal_hwint_allocator
RESTOREALL
%endmacro
硬件中断分发器函数
* @brief 硬件中断分发器函数
* 1. 调用异常处理函数hal_do_hwint
*
* @param intnumb 第一个参数为中断编号,在rdi
* @param krnlsframp 第二个参数为中断发生时的栈指针,在rsi
*/
void hal_hwint_allocator(uint_t intnumb, void *krnlsframp)
{
cpuflg_t cpuflg;
hal_cpuflag_sti(&cpuflg);
hal_hwint_eoi();
hal_do_hwint(intnumb, krnlsframp);
krlsched_chkneed_pmptsched();
hal_cpuflag_cli(&cpuflg);
//kprint("暂时无法向任何服务进程发送中断消息,直接丢弃......\n");
return;
}
/**
* @brief 中断处理函数
* 1. 加锁
* 2. 调用中断回调函数hal_run_intflthandle
* 3. 释放锁
*
* @param intnumb 中断码
* @param krnlsframp
*/
void hal_do_hwint(uint_t intnumb, void *krnlsframp)
{
...
// 运行中断处理的回调函数
hal_run_intflthandle(intnumb, krnlsframp);
...
}
最后调用中断处理的回调函数
/**
* @brief 负责调用中断处理的回调函数
* 1. 先获取中断异常表machintflt
* 2. 然后调用i_serlist 链表上所有挂载intserdsc_t 结构中的中断处理的回调函数,是否处理由函数自己判断
*
* @param ifdnr 中断码
* @param sframe
*/
void hal_run_intflthandle(uint_t ifdnr, void *sframe)
{
intserdsc_t *isdscp;
list_h_t *lst;
// 根据中断号获取中断异常描述符地址
intfltdsc_t *ifdscp = hal_retn_intfltdsc(ifdnr);
if (ifdscp == NULL) {
hal_sysdie("hal_run_intfdsc err");
return;
}
// 遍历i_serlist链表
list_for_each(lst, &ifdscp->i_serlist) {
// 获取i_serlist链表上对象即intserdsc_t结构
isdscp = list_entry(lst, intserdsc_t, s_list);
// 调用中断处理回调函数
isdscp->s_handle(ifdnr, isdscp->s_device, sframe);
}
return;
}
通过isdscp->s_handle(ifdnr, isdscp->s_device, sframe);就会访问到对应的回调函数,比如 键盘驱动的就是
/**
* @brief 键盘驱动回调函数
*
* @param ift_nr
* @param devp
* @param sframe
* @return drvstus_t
*/
drvstus_t uart_handle(uint_t ift_nr, void *devp, void *sframe)
{
device_t* idev = (device_t*)devp;
kboard_t* ikbd = (kboard_t*)idev->dev_extdata;
u8_t scan;
if (NULL == idev || NULL == ikbd) {
return DFCERRSTUS;
}
scan = (u8_t)read_keybd();
kbd_write_scan(ikbd, scan);
krlsem_up(&ikbd->kb_sem);
return DFCOKSTUS;
}