Linux 字元設備驅動程序問題
我正在編寫 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的根源。