我正在编写 LKM 来创建字符设备驱动程序。
Linux 内核:VirtualBox 中的 4.4.0-93-generic,2GB 内存,SWAP 为 300Kb
问题 1
如果我编写一个处理 dev_write 中的fd的 C 程序,一切都很好,它应该按原样读取,但是如果我尝试使用head -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可能是因为
head
没有看到行尾字符(例如换行符,'\n'
),或者它使用了 seek 系统调用,而您忽略了and函数offset
中的参数(这意味着 seek 将不起作用,如果我正确理解它)...检查一下- head 确实尝试使用搜索来优化事物,但不确定它是否适用于您的情况。dev_read()
dev_write()
也不确定您关于问题 2是由时间不同步引起的答案是否正确(除非它与...有关
msleep()
)...我的猜测是内存分配问题或竞争条件,但您没有向我们展示来源代码push()
,pop()
所以我们不知道。看起来您只是存储
buffer
andlen
参数dev_write()
,然后使用它们dev_read()
传递给copy_to_user()
...该缓冲区中的数据仍将在用户空间中,因此您可能正在尝试从用户空间复制到用户空间。阅读本文可能会有所帮助。push()
您应该使用and ...的代码更新您的问题,pop()
但至少push()
需要为要插入列表中的链表元素和用于保存写入数据的缓冲区分配内存,然后用于copy_from_user()
获取数据超出用户空间并进入内核缓冲区......然后,在完成msg
in之后dev_read()
,您将需要释放包含在其中的内核缓冲区msg
,然后释放msg
自身。我知道这里有很多复制,但要避免这种情况,您必须非常努力地使用虚拟内存系统和代码设计(即零复制实现)。
还有一件小但非常重要的事情,
dev_read()
因为您没有检查,message_length
即<= len
缓冲区中有足够的空间用于消息。例如,就您的代码而言,您的驱动程序可能会尝试复制大于可用空间的消息。copy_to_user()
应该抓住这一点,但话又说回来,这可能是您的问题 2的根源。我解决了问题 2。发生这种情况是因为 virtualbox 与主机不同步。
在这里找到解决方案 https://stackoverflow.com/questions/5308492/virtualbox-synchronisation-problems
这是:
问题 1
我搜索了我的提交历史,发现了一条提交消息,上面写着“Working test.sh”,这意味着它正在通过写入
echo
和读取head -n 1
在此提交中,我的 dev_read 函数返回消息长度。
这似乎适用于上述 C 程序的和缓冲读取
head -n 1
。cat
我在这里找到了答案: https ://www.tldp.org/LDP/lkmpg/2.4/html/c577.htm