代码文件目录
在B站录了一个简单的视频讲解,有需要的同学可以去康康。
链接: 操作系统实验—字符设备驱动实现聊天程序.
此实验的最重要的思路:
聊天程序通过循环读取字符设备数据来达到接收消息的功能,而使用非阻塞键盘读取输入来达到发送消息的功能。主要是学会如何使用字符设备,熟悉字符设备的一些操作函数。
文件夹结构图如下:
其中只有chardev.c和chat.c和do.sh和del.sh和Makefile是自行编写程序.
chardev.c 字符驱动程序
//chardev.c 字符驱动程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#define MAX_DEVICES 10
#define MAJOR_NUM 101
#define MINOR_NUM 0
#define BUFFER_SIZE 1048576
struct _device_data{
struct cdev chardev;
unsigned char *buffer;
int npos;
}*mydata[MAX_DEVICES];
static ssize_t device_read(struct file *filp, char * buff, size_t len, loff_t* offset)
{
int nlen=len;
struct _device_data* pdb=filp->private_data;
if(nlen>pdb->npos-*offset)
nlen=pdb->npos-*offset;
if(copy_to_user(buff, (pdb->buffer)+*offset, nlen))
return -EFAULT;
*offset += nlen;
return nlen;
}
static ssize_t device_write(struct file *filp, const char * buff, size_t len, loff_t* offset)
{
int nlen=len;
struct _device_data* pdb=filp->private_data;
if(nlen>BUFFER_SIZE-pdb->npos)
nlen=BUFFER_SIZE-pdb->npos;
if(nlen==0)
return -ENOMEM;
if(copy_from_user(&pdb->buffer[pdb->npos], buff, nlen))
return -EFAULT;
pdb->npos += nlen;
return nlen;
}
static int device_open(struct inode *inode, struct file * filp)
{
int nminor=iminor(inode);
if(!mydata[nminor]->buffer)
mydata[nminor]->buffer=(unsigned char *)vmalloc(BUFFER_SIZE);
if(!mydata[nminor]->buffer)
return -ENOMEM;
filp->private_data=mydata[nminor];
if((filp->f_flags&O_ACCMODE)==O_WRONLY)
mydata[nminor]->npos=0;
return 0;
}
static int device_release(struct inode* inode, struct file* filp)
{
/*
int i;
for(i=0; i<MAX_DEVICES; ++i){
cdev_del(&mydata[i]->chardev);
if(mydata[i]->buffer)
vfree(mydata[i]->buffer);
kfree(mydata[i]);
}
*/
return 0;
}
struct file_operations fops = {
.owner=THIS_MODULE,
.read=device_read,
.write=device_write,
.open=device_open,
.release=device_release,
};
static int device_init(void)
{
int i, ndev, ret;
printk(KERN_INFO "Loading " KBUILD_MODNAME "...\n");
for(i=0; i<MAX_DEVICES; ++i) {
mydata[i] =(struct _device_data*)kmalloc(sizeof(*mydata[0]), GFP_KERNEL);
if(!mydata[i]){
printk(KERN_EMERG "Can't allocate memory to mydata\n");
return -ENOMEM;
}
mydata[i]->buffer=NULL;
mydata[i]->npos=0;
cdev_init(&mydata[i]->chardev, &fops);
mydata[i]->chardev.owner=THIS_MODULE;
ndev=MKDEV(MAJOR_NUM, MINOR_NUM+i);
ret=cdev_add(&mydata[i]->chardev, ndev,1);
if(ret){
printk(KERN_EMERG "Can't register device[%d]!\n", i);
return -1;
}
}
return 0;
}
static void device_exit(void)
{
int i;
printk(KERN_INFO "Unloading " KBUILD_MODNAME "...\n");
for(i=0; i<MAX_DEVICES; ++i){
cdev_del(&mydata[i]->chardev);
if(mydata[i]->buffer)
vfree(mydata[i]->buffer);
kfree(mydata[i]);
}
}
module_exit(device_exit);
module_init(device_init);
MODULE_LICENSE("GPL");
Makefile
# Makefile5.3
obj-m := chardev.o
PWD := $(shell pwd)
KVER ?= $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) #注意这里不是空格,是tab
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.mod *.symvers *.order
do.sh shell程序(安装字符设备)
#do.sh shell程序(安装字符设备)
sudo insmod ./chardev.ko #安装设备驱动模块
lsmod | grep chardev #显示安装的设备驱动模块
sudo mknod /dev/chardev0 c 101 0 #创建设备文件,含以下共10个
sudo mknod /dev/chardev1 c 101 1
sudo mknod /dev/chardev2 c 101 2
sudo mknod /dev/chardev3 c 101 3
sudo mknod /dev/chardev4 c 101 4
sudo mknod /dev/chardev5 c 101 5
sudo mknod /dev/chardev6 c 101 6
sudo mknod /dev/chardev7 c 101 7
sudo mknod /dev/chardev8 c 101 8
sudo mknod /dev/chardev9 c 101 9
sudo chmod 666 /dev/chardev*
del.sh shell程序(卸载字符设备)
#del.sh shell程序(卸载字符设备)
sudo rm /dev/chardev0
sudo rm /dev/chardev1
sudo rm /dev/chardev2
sudo rm /dev/chardev3
sudo rm /dev/chardev5
sudo rm /dev/chardev6
sudo rm /dev/chardev7
sudo rm /dev/chardev8
sudo rm /dev/chardev9
sudo rmmod chardev
dmesg | tail -1 #倒数1条信息
chat.c 聊天程序
//chat.c 聊天程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <memory.h>
#include <unistd.h>
#include <termios.h>
int kbhit(void);
int main()
{
int fd,i;
char msg[101];
fd=open("/dev/chardev0", O_RDWR|O_APPEND);
if(fd==-1){
fprintf(stderr, "can't openfile chardev0\n");
exit(0);
}
while(1){
if( kbhit() )
{
char k[100]; //用来存储键盘输入的数据
char head[150]; //存放消息头信息和数据,表明是哪个进程写入的消息
int pid = getpid();
fgets(k,100,stdin);
//printf("有按键按下,开始读取键盘输入信息,并写入字符设备\n");
//printf("我是进程%d,我写入的数据为:%s\n",pid,k);
sprintf(head,"我是进程%d:%s",pid,k);
write(fd,head,strlen(head));//写入字符设备
}
for(i=0;i<101;i++)
msg[i]='\0';
read(fd,msg,100);
if(strlen(msg) != 0 ) { //要有新数据才会显示在屏幕上
//printf("读出的数据为:\n%s\n",msg);
printf("%s\n",msg);
}
sleep(2);
}
close(fd);
return 0;
}
//非阻塞检测按键函数
int kbhit(void)
{
struct termios oldt, newt;
int ch,oldf;
tcgetattr(STDIN_FILENO, &oldt);
newt=oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
oldf=fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
ch=getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
fcntl(STDIN_FILENO, F_SETFL, oldf);
if(ch != EOF) {
ungetc(ch, stdin);
return 1;
}
return 0;
}
实验进行步骤:
1.把上述代码全部编辑好。
2.在终端输入“make”命令,编译字符驱动。
3.给do.sh和del.sh特权。命令是:chmod +x do.sh 和 chmod +x del.sh
4.运行do.sh安装字符驱动 方法:./do.sh
5.编译chat.c文件,方法:gcc -o chat chat.c
6.运行两次chat程序。方法:./chat
7.开始键盘输入聊天内容。
程序运行结果显示: