您的位置:首页 > 其它

Raw-OS源码分析之信号量释放与获取

2014-05-20 09:47 204 查看
分析的内核版本截止到2014-04-15,基于1.05正式版,blogs会及时跟进最新版本的内核开发进度,若源码注释出现”???”字样,则是未深究理解部分。

Raw-OS官方网站:http://www.raw-os.org/

Raw-OS托管地址:https://github.com/jorya/raw-os/



先看一个有关任务之间用信号量同步的例子:



一、信号量同步例子

1.task B先执行1过程时,由于不能获取信号量A而阻塞在信号量A的阻塞链表上

2.task B阻塞后,调度task A执行,首先释放信号量A,然后继续往下执行,因为不能获取信号量B而阻塞在信号量B的阻塞链表上

3.task A阻塞后,调度task B执行,因为在task A中释放了信号量A,所以task B执行,然后释放信号量B,task B完成,调度task A继续执行

4.由于task B释放了信号量B,task A得以执行



二、Raw-OS创建信号量

创建信号量一般就是为创建一个信号量控制块,然后初始化该信号量控制块:







具体看代码如何实现:

RAW_U16 raw_semaphore_create(RAW_SEMAPHORE *semaphore_ptr, RAW_U8 *name_ptr, RAW_U32 initial_count)
{
	/* 检查要创建的信号量是否有实体定义,没有定义时返回 */
	#if (RAW_SEMA_FUNCTION_CHECK > 0)
	if (semaphore_ptr == 0) {
		return RAW_NULL_OBJECT;
	}
	/* 检查初始化信号量计数器值,超过设定范围时返回 */
	if (initial_count == 0xffffffff) {
		return RAW_SEMAPHORE_OVERFLOW;
	}
	#endif

	/* 初始化信号量的阻塞链表 */
	list_init(&semaphore_ptr->common_block_obj.block_list);

	/* 初始化信号量计数器初始化值 */
	semaphore_ptr->count     = initial_count;
	/* 初始化信号量名称 */
	semaphore_ptr->common_block_obj.name = name_ptr;
	/* 设置信号量阻塞方式:按优先级??? */
	semaphore_ptr->common_block_obj.block_way = RAW_BLOCKED_WAY_PRIO;
	/* 初始化信号量回调函数为NULL */
	semaphore_ptr->semphore_send_notify = 0;
	/* 设置信号量类型:信号量对象??? */
	semaphore_ptr->common_block_obj.object_type = RAW_SEM_OBJ_TYPE;
	/* trace系统信号量调试??? */
	TRACE_SEMAPHORE_CREATE(raw_task_active, semaphore_ptr);

	return RAW_SUCCESS;

}


三、获取信号量

由之前的同步例子我们很容易地使用信号量,现在我们来分析一下获取信号量,特别是不能获取信号量时内核如何发生任务阻塞








很显然,如果当前运行任务获取信号量成功时会立即返回,但是当不能获取信号量时,就会发生任务阻塞,具体细分为4个步骤:

1.将任务设置成RAW_PEND或者按照阻塞超时按超时时间大小插入到tick_list

2.将任务从运行队列中移除

3.将任务按优先级大小加入到信号量的阻塞链表中

4.执行系统调度



信号量获取代码:

RAW_U16 raw_semaphore_get(RAW_SEMAPHORE *semaphore_ptr, RAW_TICK_TYPE wait_option)
{

	RAW_U16 error_status;
	/* CPU状态机变量 */
	RAW_SR_ALLOC();
	/* 检查信号量控制块是否存在,不存在返回 */
	#if (RAW_SEMA_FUNCTION_CHECK > 0)
	if (semaphore_ptr == 0) {
		return RAW_NULL_OBJECT;
	}
	/* 检查中断嵌套,即中断内不能调用此函数,即中断内不能获取信号量,但中断内可以释放信号量 */
	if (raw_int_nesting) {
		return RAW_NOT_CALLED_BY_ISR;
	}
	#endif
	/* 保存CPU状态字 */
	RAW_CRITICAL_ENTER();
	/* 如果操作传入的信号量控制块,控制块内标识不是信号量对象类型时,返回 */
	if (semaphore_ptr->common_block_obj.object_type != RAW_SEM_OBJ_TYPE) {
		RAW_CRITICAL_EXIT();
		return RAW_ERROR_OBJECT_TYPE;
	}
	/* 获取信号量操作时,对信号量计数器-- */
	if (semaphore_ptr->count) {
		semaphore_ptr->count--;
		RAW_CRITICAL_EXIT();
		/* trace系统调试??? */
		TRACE_SEMAPHORE_GET_SUCCESS(raw_task_active, semaphore_ptr);
		return RAW_SUCCESS;
	}

	/* 如果信号量的阻塞方式是RAW_NO_WAIT时,即信号量不阻塞操作它的任务,就马上返回 */
	if (wait_option == RAW_NO_WAIT) {
		RAW_CRITICAL_EXIT();
		return RAW_NO_PEND_WAIT;
	}
	/* 处理系统锁??? */
	SYSTEM_LOCK_PROCESS();
	/* 此时任务不能获取信号量,根据wait_option设定阻塞任务在信号量的阻塞链表中 */
	raw_pend_object((RAW_COMMON_BLOCK_OBJECT *)semaphore_ptr, raw_task_active, wait_option);
	RAW_CRITICAL_EXIT();
	/* trace系统调试??? */
	TRACE_SEMAPHORE_GET_BLOCK(raw_task_active, semaphore_ptr, wait_option);
	/* 执行系统调度 */
	raw_sched();
	/* 系统调度后的新活动任务,置位任务TCB阻塞标志 */
	error_status = block_state_post_process(raw_task_active, 0);
	return error_status;

}


阻塞部分代码:

RAW_U16 raw_pend_object(RAW_COMMON_BLOCK_OBJECT *block_common_obj, RAW_TASK_OBJ *task_ptr, RAW_TICK_TYPE timeout)
{
	/*
	 * 因为在调用raw_pend_object之前已经处理过timeout为0时的情况,即wait_option==RAW_NO_WAIT
	 * 所以这里再次发生timeout==0就会不正确,所以系统检测到timeout==0时,系统down掉
	 *
	 * 例如raw_semaphore_get中会先检查wait_option == RAW_NO_WAIT的阻塞情况
	 */
	if (timeout == 0u) {
		RAW_ASSERT(0);
	}

	/* 将阻塞任务对象传入到任务阻塞对象指针,作为记录 */
	task_ptr->block_obj = block_common_obj;

	/* 如果阻塞类型是永久阻塞,设置任务状态为阻塞态 */
	if (timeout == RAW_WAIT_FOREVER) {
		task_ptr->task_state = RAW_PEND;
	}
	/* 如果不是永久阻塞态,就会带有阻塞超时参数,那么就插入到tick list中,等待超时 */
	else {
		/* 将阻塞任务插入到tick list中,等待超时 */
		tick_list_insert(task_ptr,timeout);
		/* 加入到tick list时设置任务状态是阻塞超时态 */
		task_ptr->task_state = RAW_PEND_TIMEOUT;
	}

	/* 发生阻塞时将此任务从就绪队列的对应位置中移除 */
	remove_ready_list(&raw_ready_queue, task_ptr);

	/* 如果,阻塞对象为FIFO机制(先入先出)时,则将被阻塞对象加入到阻塞对象的阻塞链表的末端 */
	if (block_common_obj->block_way == RAW_BLOCKED_WAY_FIFO) {
		list_insert(&block_common_obj->block_list, &task_ptr->task_list);
	}
	/* 如果不是FIFO机制,则按优先级大小插入到阻塞对象的阻塞链表的适当位置 */
	else {
		add_to_priority_list(&block_common_obj->block_list, task_ptr);
	}

	return RAW_SUCCESS;
}


四、释放信号量

释放信号量就是为了被阻塞在信号量的任务重新唤醒而得到执行,释放信号量可以在其他任务函数中发生,也可以在中断ISR中发生








1.在其他代码位置释放信号量

2.当信号量阻塞链表为空时,信号量计数器+1,调用通知回调函数

3.当信号量阻塞链表非空时,根据唤醒方式唤醒阻塞链表中最高优先级任务或者阻塞链表中所有任务

4.唤醒任务后执行系统调度



RAW_U16 raw_semaphore_put(RAW_SEMAPHORE *semaphore_ptr)
{
	/* 检查要释放的信号量控制块是否存在,不存在时返回 */
	#if (RAW_SEMA_FUNCTION_CHECK > 0)
	if (semaphore_ptr == 0) {
		return RAW_NULL_OBJECT;
	}
	#endif

	/* 当开启raw-os的0中断特性时,由0中断任务转发??? */
	#if (CONFIG_RAW_ZERO_INTERRUPT > 0)
	if (raw_int_nesting && raw_sched_lock) {
		return int_msg_post(RAW_TYPE_SEM, semaphore_ptr, 0, 0, 0, 0);
	}
	#endif

	/* 调用信号量内核API,释放信号量 */
	return semaphore_put(semaphore_ptr, WAKE_ONE_SEM);
}


系统具体实现:

RAW_U16 semaphore_put(RAW_SEMAPHORE *semaphore_ptr, RAW_U8 opt_wake_all)
{
	LIST *block_list_head;
	/* 定义CPU状态字变量 */
	RAW_SR_ALLOC();
	/* 保存CPU状态字变量 */
	RAW_CRITICAL_ENTER();
	/* 如果操作传入的信号量控制块,控制块内标识不是信号量对象类型时,返回 */
	if (semaphore_ptr->common_block_obj.object_type != RAW_SEM_OBJ_TYPE) {
		RAW_CRITICAL_EXIT();
		return RAW_ERROR_OBJECT_TYPE;
	}
	/* 获取传入信号量控制块的阻塞链表头 */
	block_list_head = &semaphore_ptr->common_block_obj.block_list;

	/* 当判断信号量控制块的阻塞链表为空时,执行对应操作 */
	if (is_list_empty(block_list_head)) {
		/* 检查信号量计数器,超过范围时,返回 */
		if (semaphore_ptr->count == RAW_SEMAPHORE_COUNT) {
			RAW_CRITICAL_EXIT();
			/* trace系统调试??? */
			TRACE_SEMAPHORE_OVERFLOW(raw_task_active, semaphore_ptr);
			return RAW_SEMAPHORE_OVERFLOW;
		}
		/* 当阻塞链表为空,信号量计数器未超出范围时,计数器++ */
		semaphore_ptr->count++;

		RAW_CRITICAL_EXIT();

		/* 如果对信号量注册了回调函数,这里会调用此回调函数 */
		if (semaphore_ptr->semphore_send_notify) {
			semaphore_ptr->semphore_send_notify(semaphore_ptr);
		}
		/* trace系统调试??? */
		TRACE_SEMAPHORE_COUNT_INCREASE(raw_task_active, semaphore_ptr);
		return RAW_SUCCESS;
	}

	/* 当释放信号量时加入唤醒所有标志,在while循环中唤醒所有阻塞在该信号量阻塞链表上的任务 */
	if (opt_wake_all) {
		/* 链表整个阻塞链表,唤醒所有阻塞任务 */
		while (!is_list_empty(block_list_head)) {
			/* 执行内核唤醒API */
			raw_wake_object(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list));
			/* trace系统调试??? */
			TRACE_SEM_WAKE_TASK(raw_task_active, list_entry(block_list_head->next, RAW_TASK_OBJ, task_list), opt_wake_all);
		}
	}
	/* 如果不是唤醒所有任务,则唤醒信号量阻塞链表中的第一个任务,即被阻塞的最高优先级任务 */
	else {
		/* 只唤醒信号量阻塞链表中的第一个任务,即最高优先级任务 */
		raw_wake_object(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list));
		/* trace系统调试??? */
		TRACE_SEM_WAKE_TASK(raw_task_active, list_entry(block_list_head->next, RAW_TASK_OBJ, task_list), opt_wake_all);
	}

	RAW_CRITICAL_EXIT();
	/* 执行系统调度 */
	raw_sched();

	return RAW_SUCCESS;
}


五、阻塞任务唤醒

唤醒任务会根据任务TCB中阻塞状态标识变量用以下3种操作组合唤醒任务







三种操作分别为:

1.从信号量阻塞链表中删除

2.加入到就绪队列

3.带timeout模式的阻塞唤醒时从tick_list中删除



具体根据代码中列出的任务阻塞状态解释:

static RAW_U16 pend_task_wake_up(RAW_TASK_OBJ *task_ptr)
{
	/* 对于不同任务状态的阻塞任务进行唤醒 */
	switch (task_ptr->task_state) {
		/* 阻塞、超时阻塞态 */
		case RAW_PEND:
		case RAW_PEND_TIMEOUT:
			/* 阻塞的任务中task_list记录着阻塞对象的阻塞链表信息,唤醒时要从阻塞对象的阻塞链表中移除 */
			list_delete(&task_ptr->task_list);
			/* 简单阻塞唤醒后加入到就绪队列中,等待调度 */
			add_ready_list(&raw_ready_queue, task_ptr);
			/* 唤醒后设置任务状态为就绪态 */
			task_ptr->task_state = RAW_RDY;
			break;
		/* 阻塞挂起、超时阻塞挂起态 */
		case RAW_PEND_SUSPENDED:
		case RAW_PEND_TIMEOUT_SUSPENDED:
			/* 唤醒后还有挂起状态存在,仅从阻塞对象的阻塞链表中移除任务 */
			list_delete(&task_ptr->task_list);
			/* 设置任务状态为挂起态 */
			task_ptr->task_state = RAW_SUSPENDED;
			break;
		default:
			RAW_ASSERT(0);
	}

	/*
	 * 阻塞态的任务被唤醒后,从tick_list中删除有关的休眠任务
	 *
	 * 这里,在阻塞时或者其他操作引起休眠时才会在tick_list中添加休眠任务的休眠信息
	 *
	 * 例如,以永久等待时间形式调用用户API信号量获取函数、互斥锁获取函数、事件获取函数等等的操作时
	 * 因为在tick_list_remove会检查任务中是否存在有时间链表头tick_head(当tick_head有值时说明在
	 * tick_list中有任务休眠信息,也等同于说,任务是以有限等待时间阻塞的)
	 *
	 * 永久等待时间不会引起任务添加到tick_list的操作,仅仅将任务状态设置为RAW_PEND,并且唤醒时进行到
	 * 此函数也不会引发tick_list的删除操作,因为在阻塞任务控制块中也不存在tick_list信息
	 */
	tick_list_remove(task_ptr);
	/* 设置任务阻塞状态 */
	task_ptr->block_status = RAW_B_OK;
	/* 阻塞任务唤醒后,阻塞对象就设置为NULL */
	task_ptr->block_obj = 0;

	return RAW_SUCCESS;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: