我的多线程用户空间应用程序和简单的内核字符驱动程序存在同步问题。如果我只需要用户应用程序或用户应用程序和模块中的同步机制,我感兴趣。
让我给你一些概述:
我有3个POSIX写线程可以打开一个字符设备(文件),在写系统调用时,所有三个线程都将一个全局变量排入队列(static int token = 0
在每次写入后递增),再次小睡并尝试再次入队。令牌在线程回调方法中增加(在写入操作之后),直到全局变量达到100.并且类似地有3个读取线程将启动并调用文件read()系统调用并尝试将令牌出列并打印在控制台(printk),小睡并重复该过程,直到队列为空。
我有一个简单的字符驱动程序,它有open(),close()read()和write()。驱动程序有一个Fifo队列实现(myqueue.c),在读取()时,一个变量从队列中删除并复制到用户空间(value_remove_from_queue()和copy_to_user())并在write()变量插入队列时( copy_from_user()和value_insert_to_queue())。
非常简单。但是我确实有一些同步问题。
我尝试在用户空间应用程序中使用互斥锁。在线程回调中,令牌递增的位置。例如:
static int token = 1;
#define MAX_TOKEN 100
void write_callback(void* p)
{
while( token <= MAX_TOKEN)
{
write(fd,buffer, 10);
mutex_lock(&mymutex);
token++;
mutex_unlock(&mymutex);
usleep(1);//1 second
}
}
字符驱动程序中的Write()类似于:
static ssize_t syncdevice_write(struct file *f, const char __user *buf,
size_t len, loff_t *off)
{
printk("(sync device) write()\n");
if( pQueue == 0 )
{
printk("(sync device) write() Queue does not exist.\n");
return 0;
}
int size = RBuffer_Size(pQueue);
printk("(sync device) write() Queue size = %d\n", size);
long token = 0;
char write_buffer[100];
memset(write_buffer, 0, 100);
if( copy_from_user(write_buffer, buf, strlen(buf)+1))
{
token = atol( write_buffer);
printk("(sync device) write long value = %ld\n", token);
if(token)
{
RBuffer_Insert(pQueue, token);
}
}
return len;
}
我的问题是内核代码中的任何同步都是必要的吗?因为我已经在用户空间应用程序中使用了互斥锁。我确实看到意外结果(例如)令牌没有正确递增(多次插入令牌值1)。
答案 0 :(得分:1)
关于互斥锁使用的一般规则是,对受保护变量的所有访问应在互斥锁定下执行。
在代码中访问token <= MAX_TOKEN
中的 token 变量时,条件检查不受互斥锁的保护。将令牌写入缓冲区(对于write()它)不会显示在您的代码段中,但也应该在关键部分内执行。
至于write()
调用本身,它取决于你的目的,它是否应该在临界区内执行。如果你想要在内核队列中精确地设置令牌,那么它应该:
void write_callback(void* p)
{
mutex_lock(&mymutex);
while(token <= MAX_TOKEN)
{
int size = snprintf(buffer, sizeof(buffer), "%d", token);
write(fd, buffer, size);
token++;
// Reaquire mutex for the next iteration. Also avoid sleeping with mutex held.
mutex_unlock(&mymutex);
usleep(1);
mutex_lock(&mymutex);
}
mutex_unlock(&mymutex);
}
如果你只想在内核队列中观察所有可能的令牌(从1到100),但它们的顺序并不重要(例如,它可以被2,3,1,4 ......),你可以在关键部分之外移动write()
来电,例如将它放在usleep(1)
之前。
在任何情况下,内核的队列实现都应该正确处理并发的 RBuffer_Insert()调用。