1. 程式人生 > 實用技巧 >Linux作業系統程式設計 實驗四 字元裝置實驗

Linux作業系統程式設計 實驗四 字元裝置實驗

實驗目的

1、瞭解Linux作業系統中的裝置驅動程式的組成
2、編寫簡單的字元裝置驅動程式並進行測試
3、理解Linux作業系統的裝置管理管理機制
4、實驗內容:
編寫Makefile檔案,使之具備如下功能:

  • 輸入make,將自動編譯scull.c和scull_test.c兩個檔案,並生成scull.o和scull_test檔案
  • 輸入make clean-all,將清除生成的所有檔案
  • 輸入make driver和make clean-driver,則分別實現生成和刪除scull.o檔案
  • 輸入make test和make clean-test,則分別實現生成和刪除scull_test檔案

編寫一個簡單的字元裝置驅動程式,要求實現如下5個基本操作:

  • scull_open()
  • scull_write()
  • scull_read()
  • scull_ioctl()
  • scull_release()

編寫一個測試程式用來測試使用者所編寫的字元裝置驅動程式

實驗過程

我的虛擬機器版本Ubuntu 20.04.1 x64,核心版本5.4.0-42-generic。

切換到root許可權,隨後編寫scull.h、scull.c、scull_test.c和Makefile

scull.h

點選檢視詳細內容
#ifndef _SCULL_H
#define _SCULL_H

struct scull_dev {
        void *data;   
        int quantum;  // the current quantum size
        int qset;  // the current array size
        unsigned long size;
        unsigned int access_key;  // used by sculluid and scullpriv
        unsigned int usage;  // lock the device while using it
        unsigned int new_msg;
        struct scull_dev *next;
};

struct scull_dev scull;

#include <linux/ioctl.h>

#define SCULL_MAJOR 111
#define SCULL_NAME "scull"
#define DEVICE_FILE "/dev/scull"

#define SCULL_MAGIC SCULL_MAJOR
#define SCULL_RESET _IO(SCULL_MAGIC,0)  // reset the data
#define SCULL_QUERY_NEW_MSG _IO(SCULL_MAGIC,1)  // check for new message
#define SCULL_QUERY_MSG_LENGTH _IO(SCULL_MAGIC,2)  // get message length
#define IOC_NEW_MSG 1

#endif

scull.c

點選檢視詳細內容
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/uaccess.h>
#include "scull.h"

MODULE_LICENSE("GPL");
int new_msg;

static int Device_Open = 0;

int scull_open(struct inode *inode, struct file *filp){
        Device_Open++;
        printk("Char device %s is in open\n", SCULL_NAME);
        try_module_get(THIS_MODULE);
        return 0;
}

ssize_t scull_write(struct file *filp, const char *buffer, size_t count, loff_t *off){
	int cfu;

	if(count < 0)
		return -EINVAL;
	if(scull.usage || scull.new_msg)
		return -EBUSY;
	scull.usage = 1;
        	kfree(scull.data);
	scull.data = kmalloc(sizeof(char) * (count + 1), GFP_KERNEL);
	if(!scull.data){
		return -ENOMEM;
	}
	cfu = copy_from_user(scull.data, buffer, count+1);
	scull.usage=0;
	scull.new_msg=1;
	return count;
}

ssize_t scull_read(struct file *filp, char *buffer, size_t count, loff_t *off){
	int length, ctu;
	if(count < 0)
		return -EINVAL;
	if(scull.usage)
		return -EBUSY;
	scull.usage=1;
	if(count == 0)
		return 0;
	length = strlen(scull.data);
	if(length < count)
		count = length;
	ctu = copy_to_user(buffer, scull.data, count+1);
	scull.new_msg = 0;
	scull.usage = 0;
	return count;
}

int scull_release(struct inode *inode, struct file *filp){
        Device_Open--;
        printk("Char device %s is in release\n", SCULL_NAME);
        module_put(THIS_MODULE);

        return 0;
}

long int unlocked_ioctl(struct file *filp,unsigned int cmd,unsigned long arg){
	switch(cmd){
		case SCULL_RESET:
			kfree(scull.data);
			scull.data = NULL;
			scull.usage = 0;
			scull.new_msg = 0;			
			break;
		case SCULL_QUERY_NEW_MSG:
			if(scull.new_msg)
				return IOC_NEW_MSG;
			break;
		case SCULL_QUERY_MSG_LENGTH:
			if(scull.data == NULL) {
				return 0;
			} else {
				return strlen(scull.data);
			}
			break;
		default:
			return -ENOTTY;
	}

	return 0;
}

struct file_operations scull_chops={
	read: scull_read,
	write: scull_write,
	unlocked_ioctl: unlocked_ioctl,
	open: scull_open,
	release: scull_release
};

int init_scull(void){
	int result;
	printk("Initializing char device %s.\n", SCULL_NAME);
	result=register_chrdev(SCULL_MAJOR, SCULL_NAME, &scull_chops);
	if(result < 0){
		printk("Scull: Can't get major number!\n");
		return result;
	}

	return 0;
}

void cleanup_scull(void){
	unregister_chrdev(SCULL_MAJOR, SCULL_NAME);
	printk("Cleanup char device %s.\n", SCULL_NAME);
}


module_init(init_scull);
module_exit(cleanup_scull);

scull_test.c

點選檢視詳細內容
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include "scull.h"

void write_proc(void);
void read_proc(void);

int main(int argc, char **argv) {
	if(argc == 1) {
		puts("Usage: scull_test [write|read]");
		exit(0);
	}

	if(!strcmp(argv[1], "write")) {
		write_proc();
	}
	else if(!strcmp(argv[1], "read")) {
		read_proc();
	}
	else {
		puts("scull_test: invalid command!");
	}

	return 0;
}

void write_proc() {
	int fd, len, quit = 0;
	char buf[100];
	fd = open(DEVICE_FILE, O_WRONLY);
	if(fd <= 0) {
		printf("Error opening device file %s for writing!\n", DEVICE_FILE);
		exit(1);
	}
	printf("input 'exit' to exit!");
	while(!quit) {
		printf("\n write>>   ");
		fgets(buf, 100, stdin);
		if(!strcmp(buf, "exit\n"))
			quit = 1;
		while(ioctl(fd, SCULL_QUERY_NEW_MSG))
			usleep(1000);
		len=write(fd, buf, strlen(buf));
		if(len < 0) {
			printf("Error writing to device %s!\n", SCULL_NAME);
			close(fd);
			exit(1);
		}
		printf("%d bytes written to device %s!\n", len - 1, SCULL_NAME);
	}
	//free(buf);
	close(fd);
}

void read_proc() {
	int fd, len, quit = 0;
	char *buf = NULL;
	fd = open(DEVICE_FILE, O_RDONLY);
	if(fd<0) {
		printf("Error opening device file %s for reading!\n", DEVICE_FILE);
		exit(1);
	}
	while(!quit) {
		printf("\n read<<   ");
		while(!ioctl(fd, SCULL_QUERY_NEW_MSG))
			usleep(1000);
		// get the msg length
		len=ioctl(fd, SCULL_QUERY_MSG_LENGTH, NULL);
		if(len) {
			if(buf!=NULL)
				free(buf);
			buf = malloc(sizeof(char) * (len+1));
			len = read(fd, buf, len);
			if(len < 0) {
				printf("Error reading from device %s!", SCULL_NAME);
			}
			else {
				if(!strcmp(buf, "exit\n")) {
					ioctl(fd, SCULL_RESET);     // reset
					quit = 1;
					printf("%s\n",buf);
				}
				else 
					printf("%s\n",buf);
			}
		}
	}
	free(buf);
	close(fd);
}

Makefile

點選檢視詳細內容
obj-m+=scull.o
KDIR=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)

scull.o: scull.c
	make -C $(KDIR) M=${PWD} modules  # 在核心原始碼環境下編譯scull.c
	gcc -o scull_test.o scull_test.c  # 編譯scull_test.c 

	insmod scull.ko  # 載入scull這個字元驅動裝置

	mknod /dev/scull c 111 0  # 建立一個裝置檔案

clean-all:
	rm -rf *.ko *.mod.c *.o modules.* Module.symvers /dev/scull
driver:
	make -C $(KDIR) M=${PWD} modules  # 在核心原始碼環境下編譯scull.c
clean-driver:
	rm -f scull.o
test:
	gcc -o scull_test.o scull_test.c  # 編譯scull_test.c 
clean-test:
	rm -f scull_test.o

.PHONY: clean clean-all driver clean-driver

執行make命令

使用make test,生成可執行檔案scull_test.o,然後輸入./scull_test.o write和./scull_test.o read執行它,輸入exit退出程式

輸入rmmod scull解除安裝模組,並確認正在執行的模組中沒有scull

最後使用dmesg | grep scull,檢視日誌內容