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
", 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
", 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.
", SCULL_NAME);
	result=register_chrdev(SCULL_MAJOR, SCULL_NAME, &scull_chops);
	if(result < 0){
		printk("Scull: Can't get major number!
");
		return result;
	}

	return 0;
}

void cleanup_scull(void){
	unregister_chrdev(SCULL_MAJOR, SCULL_NAME);
	printk("Cleanup char device %s.
", 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!
", DEVICE_FILE);
		exit(1);
	}
	printf("input 'exit' to exit!");
	while(!quit) {
		printf("
 write>>   ");
		fgets(buf, 100, stdin);
		if(!strcmp(buf, "exit
"))
			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!
", SCULL_NAME);
			close(fd);
			exit(1);
		}
		printf("%d bytes written to device %s!
", 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!
", DEVICE_FILE);
		exit(1);
	}
	while(!quit) {
		printf("
 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
")) {
					ioctl(fd, SCULL_RESET);     // reset
					quit = 1;
					printf("%s
",buf);
				}
				else 
					printf("%s
",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,查看日志内容

原文地址:https://www.cnblogs.com/ast935478677/p/14167484.html