信号量的功能:
实现线程间通信的机制
实现线程之间同步
临界资源的互斥访问
那这三个点我先说第三个,首先需要解释什么是临界资源?
临界资源是指每次仅允许一个进程访问的资源。属于临界资源的硬件有打印机、磁带机等,软件有消息缓冲队列、变量、数组、缓冲区等。 诸进程间应采取互斥方式,实现对这种资源的共享。
首先我感觉这个说法有问题,我认为应该写为
表示一种公共资源或共享数据,可以被多个线程使用。但是每一次只能被一个线程使用。
线程和进程的关系,可以类比为火车和车厢
这个应该是线程间的概念,说成进程感觉大了一点,但如果不具体细分,这个百度知道的说法还是很有价值的
好咱们来理解理解,打印机要打印东西,如果一个办公室有多个电脑都连着这个打印机,那是不是一次只能打印一个东西,不可能同时打印两份东西。
类似的在软件上,变量我在访问的时候,别人就不能访问,我想了一下,之所以原来理解的不深刻的理由应该是原来一直写的裸机程序,都是一件事情执行完然后别人执行,虽然有中断,但是印象还是不很深刻。
那临界资源的互斥访问,就是说,这个资源每次只允许一个进程来访问(hhh照着又抄了一遍)
理解清楚了这一点,信号量的意思就是说,信号量当有进程在访问的时候,如果有其他的进程也想访问,那不好意思,等我用完。书看到这里的时候,我就在想,不是有优先级的概念吗,会不会存在我在访问的时候,有其他优先级比较高的进程也想访问得情况?答案是:有的—但是,rtos给出了解决方法
问题重述:比如我创建了一个线程用于调节pid,那比如我调节的速度很快比如,20k,在做电源题的时候很常见吧,开关频率都很高的。那假如我做了几次pid,就得把参数发给我的电脑,如果是线程,就直接用优先级的话,可以把串口打印的优先级调高。根据我们的经验,如果要实现监控的话,那串口的优先级必须比pid调节速率要高,但如果是比如每过5次打印一次,但是我特别关心101次pid调节后的数值怎么办?
可能会说简单嘛,我把第101次的优先级调高不就好了吗。
但是我第100次可能还没有发完,发了几个字,还没有连成一段话,101次就到了呢,因为之前我说过pid运行频率很高
那这个时候就算把101次的优先级提高,那也会使得串口助手窗口的话连不成一段完整的语句
这个时候如果是裸机的话,我比较喜欢用一个flag就是大家要说话,就用一个flag,有点类似于一个token(令牌),我用完了,flag=0,然后大家再接着用,在flag=1的情况下,别人都不能用
/事件1/
if(flag==0)
{
执行事件1
}
/事件2/
if(flag==0)
{
执行事件2
}
那信号量就类似于这么一个东西,类似于一个token,我用完了,我自然会把这个东西交给下一个人,但是在我手上的时候就得等着
当时我就相当了一句话,雷公不打吃饭人(只是比较形象,跟意思没有关系),就是不管你优先级有多高,就算你是雷公,也得等我把饭吃完
还有一个就优先级翻转的东西,极其巧妙,思想核心是为了解决,高优先级的线程等待时间过长,于是令低优先级的线程在执行的时候,获取准备打断它的线程一样高的优先级
![1.jpg](//file.elecfans.com/web2/M00/77/04/pYYBAGNgkzeASGFlAAFnqSSkREU176.jpg)
好,说了这么多直接来看个实例
这个是利用信号量实现了一个设备利用串口向一个单片机发送任意长度的数据,然后单片机解析数据
#define SAMPLE_UART4_NAME "uart4" /* 需要操作的设备 /
static struct rt_semaphore rx_sem4; / 信号量 /
static rt_device_t serial4; / 设备句柄 /
/ 接收数据回调函数 /
static rt_err_t uart4_rx_ind(rt_device_t dev, rt_size_t size)
{
/ 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 /
if (size > 0)
{
rt_sem_release(&rx_sem4);
}
return RT_EOK;
}
static char uart_ZIGBEE_get_char(void)
{
char ch;
static uint8_t rv_flag = 1;
while (rt_device_read(serial4, 0, &ch, 1) == 0)
{
rt_sem_control(&rx_sem4, RT_IPC_CMD_RESET, RT_NULL);
if(rv_flag)
rt_sem_take(&rx_sem4, RT_WAITING_FOREVER);
else if(-RT_ETIMEOUT == rt_sem_take(&rx_sem4, 100))//超时100ms未获得串口数据 100是根据时钟节拍来定 节拍单位是1/RT_TICK_PER_SECOND 秒
{
rv_flag = 1; //收完一帧
ch = '\n'; //也可以用别的当伪帧尾
return ch;
}
}
rv_flag = 0; //正常获取一个字节
return ch;
}
/ 数据解析线程 /
static void ZIGBEE_data_parsing(void)
{
char rx_temp = 0, rx_len = 0;
char data_buf[30] = {0};
while (1)
{
rx_temp = uart_ZIGBEE_get_char();
if(rx_temp != '\n')
{
data_buf[(rx_len++) % 30] = rx_temp;//防止数组越界
}
else //开始处理一帧数据
{
rx_len = 0;
rt_kprintf("uart4 received data is %s\n", data_buf);
/ 帧数据处理 /
memset(data_buf, 0, sizeof(data_buf));
}
}
}
static int uart4_ZIGBEE(void)
{
rt_err_t ret = RT_EOK;
/ 查找系统中的串口设备 /
serial4 = rt_device_find(SAMPLE_UART4_NAME);
if (!serial4)
{
rt_kprintf("find %s failed!\n", SAMPLE_UART4_NAME);
return RT_ERROR;
}
/ 初始化信号量 /
rt_sem_init(&rx_sem4, "rx_sem4", 0, RT_IPC_FLAG_FIFO);
/ 以中断接收及轮询发送模式打开串口设备 /
rt_device_open(serial4, RT_DEVICE_FLAG_INT_RX);
/ 设置接收回调函数 /
rt_device_set_rx_indicate(serial4, uart4_rx_ind);
/ 创建 serial 线程 /
rt_thread_t thread = rt_thread_create("ZIG_th", (void ()(void parameter))ZIGBEE_data_parsing,
RT_NULL, 512, RT_THREAD_PRIORITY_MAX-10, 10);
/ 创建成功则启动线程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
INIT_APP_EXPORT(uart4_ZIGBEE);
接下来是对这段代码的分析:
我们直接来看这段代码:
static int uart4_ZIGBEE(void)
{
rt_err_t ret = RT_EOK;
/* 查找系统中的串口设备 /
serial4 = rt_device_find(SAMPLE_UART4_NAME);
if (!serial4)
{
rt_kprintf("find %s failed!\n", SAMPLE_UART4_NAME);
return RT_ERROR;
}
/ 初始化信号量 /
rt_sem_init(&rx_sem4, "rx_sem4", 0, RT_IPC_FLAG_FIFO);
/ 以中断接收及轮询发送模式打开串口设备 /
rt_device_open(serial4, RT_DEVICE_FLAG_INT_RX);
/ 设置接收回调函数 /
rt_device_set_rx_indicate(serial4, uart4_rx_ind);
/ 创建 serial 线程 /
rt_thread_t thread = rt_thread_create("ZIG_th", (void ()(void parameter))ZIGBEE_data_parsing,
RT_NULL, 512, RT_THREAD_PRIORITY_MAX-10, 10);
/ 创建成功则启动线程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
INIT_APP_EXPORT(uart4_ZIGBEE);
步骤:
首先是查找串口设备,不成功返回RT_ERROR
初始化信号量,中断接收以及轮询发送打开串口设备(中断接收很重要!!!)
有中断那就有中断函数,于是设置接收回调函数
创建整个线程,即接收不定长度数据,然后解析的线程
最后一行是一个相当于自动初始化的函数,确实自动初始化十分优美!
接下来一共有两个坑得填
一个坑是中断函数应该怎么写,应该怎么进中断
一个是线程函数是什么?那自然是不断接收,直到用户暂停发送(或者说发送的间隔边长了,长到了一个值,就认为暂停发送了)
/回调函数/
static rt_err_t uart4_rx_ind(rt_device_t dev, rt_size_t size)
{
}
/* 数据解析线程 */
static void ZIGBEE_data_parsing(void)
{
}
那意思是数据解析的线程,得一直等,等到出现暂停的现象,然后咱们进入中断把信号量一释放就行
在一直等的期间,得把数据存起来
static char uart_ZIGBEE_get_char(void)
{
char ch;
static uint8_t rv_flag = 1;
while (rt_device_read(serial4, 0, &ch, 1) == 0)
{
rt_sem_control(&rx_sem4, RT_IPC_CMD_RESET, RT_NULL);
if(rv_flag)
rt_sem_take(&rx_sem4, RT_WAITING_FOREVER);
else if(-RT_ETIMEOUT == rt_sem_take(&rx_sem4, 100))//超时100ms未获得串口数据 100是根据时钟节拍来定 节拍单位是1/RT_TICK_PER_SECOND 秒
{
rv_flag = 1; //收完一帧
ch = '\n'; //也可以用别的当伪帧尾
return ch;
}
}
rv_flag = 0; //正常获取一个字节
return ch;
}
重复读取,然后一直占用这个信号量即
rt_sem_take(&rx_sem4, RT_WAITING_FOREVER);
读到最后了做一个标记,这里就是选的ch=’\n’
相当于是中断函数里面收到数据就释放,但是在处理的时候又会take这个信号量,并且 rv_flag也不会变0,后面什么打印什么的都没有
最大的好处就是,在没有收到数据的时候就信号量被占用,不会被释放,这个线程就进入阻塞态,然后就可以调用其他的线程。
原作者:骁骑校尉