我正在编写 LKM 来创建字符设备驱动程序。
Linux 内核:VirtualBox 中的 4.4.0-93-generic,2GB 内存,SWAP 为 300Kb
问题1
如果我编写一个 C 程序来处理FD在 dev_write 中,一切都很好,它读起来应该是这样,但是如果我尝试使用头 -n 1 < /dev/opsysmem它没有输出任何东西。
从设备读取代码:
int main()
{
int ret, fd;
char stringToSend[BUFFER_LENGTH];
printf("Starting device test code example...\n");
fd = open("/dev/opsysmem", O_RDWR); // Open the device with read/write access
if (fd < 0)
{
perror("Failed to open the device...");
return errno;
}
printf("Press ENTER to read back from the device...\n");
getchar();
printf("Reading from the device...\n");
ret = read(fd, receive, BUFFER_LENGTH); // Read the response from the LKM
if (ret < 0)
{
perror("Failed to read the message from the device.");
return errno;
}
printf("The received message is: [%s]\n", receive);
return 0;
}
问题2
如果我重复发送一条足够大的消息,一切都很好,我的 2MiB 缓冲区已满,然后以下消息将被丢弃。但是,如果消息较小(即每条消息 1 个字符),则它会在大约 10000 个节点后停止。这是我的链表实现的问题,是一个已知的Linux问题,还是只是我没有观察到代码中的某些内容?
当我遇到问题2时,vCPU以正弦方式节流
这是我的读、写函数:
static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset) {
Node *msg;
int ret_val;
char* message;
unsigned int message_length;
// Entering critical section
down(&sem); //wait state
msg = pop(&l, 0);
// No message? No wait!
if(!msg) {
up(&sem);
return -EAGAIN;
}
if(len < msg->length) {
up(&sem);
return -EINVAL;
}
// Since we have a message, let's send it!
current_size -= message_length;
// copy_to_user has the format ( * to, *from, size) and returns 0 on success
ret_val = copy_to_user(buffer, msg->string, message_length);
if (!ret_val) {
remove_element(&l, 0);
up(&sem);
return ret_val;
} else {
up(&sem);
return -EFAULT; // Failed
}
}
static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) {
Node *n;
// buffer larger than 2 * 1024 bytes
if(len > MAX_MESSAGE_SIZE || len == 0) {
return -EINVAL;
}
n = kmalloc(sizeof(Node), GFP_KERNEL);
if(!n) {
return -EAGAIN;
}
n->string = (char*) kmalloc(len, GFP_KERNEL);
n->length = len;
copy_from_user(n->string, buffer, len);
// Enter critical section
down(&sem); //wait state
if(SLEEP) msleep(100);
// buffer is larger than the total list memory (2MiB)
if(current_size + len > MAX_LIST_SIZE) {
up(&sem);
return -EAGAIN;
}
current_size += len;
push(&l, n);
up(&sem);
// Exit critical section
return len;
}
这是我的链表及其功能
typedef struct Node {
unsigned int length;
char* string;
struct Node *next;
} Node;
typedef struct list{
struct Node *node;
} list;
static void init(list * l){
l->node = NULL;
}
static void destroyNode(Node *n) {
if(n) {
destroyNode(n->next);
kfree(n->string);
n->string = NULL;
kfree(n);
n = NULL;
}
}
static void destroy(list *l){
if(l) {
destroyNode(l->node);
}
}
static Node* pop(list *l, unsigned int index) {
struct Node *_current = l->node;
// Cut down index until reaching the desired position
while(_current) {
if(index) {
_current = _current->next;
index--;
} else { return _current; }
}
// If you are here, the node does not exist
return NULL;
}
static int push(list * l, Node *n) {
if(!n) { return -1; }
// Initialize the string
// Do we have a node in the list?
if (l->node) {
// Virtually add it as a head
n->next = l->node;
} else {
n->next = NULL;
} // Otherwise prepare the list to have no tail
// Now make the list point to the head
l->node = n;
return 0;
}
static int remove_element(list * l, unsigned int index){
// Get the reference for head
struct Node *previous;
struct Node *_current;
previous = NULL;
_current = (Node*) l->node;
// Swap _current until index
while(_current) {
// Is the index !0 and we have more nodes?
if(index) {
previous = _current;
_current = _current->next;
index--;
} else {
if(previous) {
previous->next = _current->next;
} else {
l->node = _current->next;
}
// Free memory, assign NULL pointer
kfree(_current->string);
_current-> string = NULL;
kfree(_current);
_current = NULL;
// Return success
return 0;
}
}
// No _current? No problem!
return -1;
}
关于 __ 问题 2 __ 的更新 __ 我尝试了不同大小的输入字符串,结果发现:在对设备驱动程序进行大约 650 次调用(大小为 3.3k)之后,消息列表大小变为 4MiB(这是最大值)。对设备再进行几次调用,然后内核就会冻结。
编辑1:我根据评论更新了 te dev_write 并删除了调试代码 编辑2:添加了更多功能:push/pop/destroy 编辑3:我检查了缓冲区长度与消息长度
答案1
我认为问题1可能是因为head
没有看到行尾字符(例如换行符,'\n'
),或者它使用了seek系统调用并且您忽略了and函数offset
中的参数(这意味着如果我理解正确的话,seeks将不起作用) ... 查看dev_read()
dev_write()
这out - head 确实尝试使用搜索来优化事物,但不确定它是否适用于您的情况。
也不确定你的答案问题2由时间不同步引起的是正确的(除非它与 相关msleep()
)...我的猜测是内存分配问题或竞争条件,但你没有向我们展示源代码,push()
所以pop()
我们不能告诉。
看起来您只是存储buffer
和len
参数dev_write()
,然后使用它们dev_read()
传递给copy_to_user()
...该缓冲区中的数据仍将位于用户空间中,因此您可能会尝试从用户空间复制到用户空间。阅读这可能有帮助。
push()
您应该使用and ...的代码更新您的问题pop()
,但至少push()
需要为要插入列表中的链表元素和用于保存写入数据的缓冲区分配内存,然后用于copy_from_user()
获取数据退出用户空间并进入内核缓冲区...然后,在完成 in 后msg
,dev_read()
您将需要释放 中 包含的内核缓冲区msg
,然后释放msg
自身。
我知道这里发生了大量的复制,但是为了避免这种情况,您必须非常努力地使用虚拟内存系统和代码设计(即零拷贝执行)。
还有一件小但非常重要的事情,即dev_read()
您不检查缓冲区中是否有足够的空间容纳消息message_length
。<= len
例如,按照您的代码,您的驱动程序可能会尝试复制大于可用空间的消息。copy_to_user()
应该抓住这一点,但话又说回来,也许这就是你的来源问题2。
答案2
我解决了问题2。发生这种情况是因为 virtualbox 与主机不同步。
在这里找到了解决方案 https://stackoverflow.com/questions/5308492/virtualbox-synchronization-problems
这是:
VBoxManage guestproperty 设置“/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold”1000
答案3
问题1
我搜索了我的提交历史记录,发现一条提交消息显示“Working test.sh”,这意味着它正在通过写入echo
和读取head -n 1
在此提交中,我的 dev_read 函数返回消息长度。
这似乎适用于上述 C 程序中的 和 缓冲读取head -n 1
。cat