1.字符设备驱动
只能一个字节一个字节的读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后顺序进行。字符设备是面向流的设备,常见的字符设备如鼠标、键盘、串口、控制台、LED等。在本实验中,我们实现一个简单的字符设备驱动实验,并在设备的打开操作中打印主次设备号。
在工作目录下建立device_driver.c文件:
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/init.h>
#include<linux/cdev.h>
#define DEMO_NAME "my_demo_dev"
static dev_t dev;
static struct cdev* demo_cdev;
static signed count=1;
static int demodrv_open(struct inode* inode, struct file* file)
{
int major=MAJOR(inode->i_rdev);
int minor=MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n",__func__, major, minor);
return 0;
}
static ssize_t demodrv_read(struct file* file, char __user* buf, size_t lbuf,loff_t* ppos)
{
printk("%s enter\n",__func__);
return 0;
}
static ssize_t demodrv_write(struct file* file, const char __user* buf, size_t count, loff_t* f_pos)
{
printk("%s enter\n",__func__);
return 0;
}
static const struct file_operations demodrv_fops={
.owner=THIS_MODULE,
.open=demodrv_open,
.read=demodrv_read,
.write= demodrv_write,
};
static int __init simple_char_init(void)
{
int ret;
ret=alloc_chrdev_region(&dev,0,count,DEMO_NAME);
if(ret)
{
printk("failed to allocate char device region\n");
return ret;
}
demo_cdev=cdev_alloc();
if(!demo_cdev)
{
printk("cedv_alloc_failed\n");
goto unregister_chrdev;
}
cdev_init(demo_cdev, &demodrv_fops);
ret=cdev_add(demo_cdev, dev, count);
if(ret)
{
printk("cdev_add failed\n");
goto cdev_fail;
}
printk("successed register char device: %s\n", DEMO_NAME);
printk("Major number=%d, Minor number=%d\n", MAJOR(dev), MINOR(dev));
return 0;
cdev_fail:
cdev_del(demo_cdev);
unregister_chrdev:
unregister_chrdev_region(dev, count);
return ret;
}
static void __exit simple_char_exit(void)
{
printk("removing device\n");
if(demo_cdev){
cdev_del(demo_cdev);
}
unregister_chrdev_region(dev, count);
}
MODULE_LICENSE("GPL");
module_init(simple_char_init);
module_exit(simple_char_exit);
下面是makefile文件:
obj-m:= device_driver.o
CURRENT_PATH:= $(shell pwd)
LINUX_KERNEL:= $(shell uname -r)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
如上述代码所示,实现一个基本的字符驱动设备需要以下几个部分:字符设备驱动模块的加载、卸载函数和file_operations结构中的成员函数。具体步骤如下:
(1)分配和释放设备号
在设备驱动程序中,注册设备前首先要向系统申请设备号,
分配设备号有静态和动态的两种方法:
静态分配(register_chrdev_region()函数)
动态分配(alloc_chrdev_region())
通过 unregister_chrdev_region()函数释放已分配的(无论是静态的还是动态的)设备号。
(2)定义并初始化一个struct file_operations结构,并实现其中的操作函数
static struct file_operations cdrv_fops = {
.owner=THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open=cdrv_open,
.read=cdrv_read,
.write=cdrv_write,
};
static int cdrv_open(struct inode *inode, struct file *filp)
static ssize_t cdrv_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
static ssize_t cdrv_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
(3)字符设备的注册
(4)删除字符设备
(5)注销设备号
(6)模块声明 MODULE_LICENSE(“GPL”);
(7)加载模块 module_init(cdrv_init);
(8)卸载模块module_exit(cdrv_exit);
3、编译模块Makefile文件
4、利用mknod命令在/dev目录下为字符设备生成对应的节点
在工作目录下make编译生成模块文件,并执行命令sudo insmod device_driver.ko加载内核模块,然后运行命令dmesg打印我们需要的设备号:
在显示信息的最后,我们也看到了主设备号major=240
2.块设备驱动
块设备驱动是指是指可以从设备的任意位置读取一定长度的数据设备。块设备如硬盘、磁盘、U盘和SD卡等存储设备。在本实验中,我们实现一个简单的块设备驱动并加载内核模块,查看相应设备号。
在工作目录下建立文件demo2.c:
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/types.h> /* size_t */
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h> /* invalidate_bdev */
#include <linux/bio.h>
#include<linux/hdreg.h>
#include<linux/slab.h>
#define MY_BLOCK_NAME "demo2_blodev"
#define MY_DEVICE_MAJOR COMPAQ_SMART2_MAJOR
#define MY_BLOCK_CAPACITY (512*1024)
struct block_device_operations blodev_fops={
.owner=THIS_MODULE
};
static unsigned int do_request(struct request_queue *q,struct bio * bio);
static struct request_queue *blodev_queue;
unsigned char blodev_data[MY_BLOCK_CAPACITY];
static struct gendisk *blodev;
static int __init my_init(void){
int ret;
blodev_queue=blk_alloc_queue(GFP_KERNEL);
blk_queue_make_request(blodev_queue,do_request);
if(!blodev_queue){
ret=-ENOMEM;
return ret;
}
blodev = alloc_disk(1);
if(!blodev){
ret=-ENOMEM;
blk_cleanup_queue(blodev_queue);
return ret;
}
strcpy(blodev->disk_name,MY_BLOCK_NAME);
blodev->major=MY_DEVICE_MAJOR;
blodev->first_minor=0;
blodev->fops=&blodev_fops;
blodev->queue=blodev_queue;
set_capacity(blodev,MY_BLOCK_CAPACITY>>9);
add_disk(blodev);
return 0;
}
static void __exit my_exit(void){
put_disk(blodev);
//del_gentrcpy(blodev->disk_name,MY_BLOCK_NAME);
blodev->major=MY_DEVICE_MAJOR;
blodev->first_minor=0;
blodev->fops=&blodev_fops;
blodev->queue=blodev_queue;
set_capacity(blodev,MY_BLOCK_CAPACITY>>9);
//isk(blodev);
blk_cleanup_queue(blodev_queue);
}
static unsigned int do_request(struct request_queue *q,struct bio*bio){
unsigned long offset=bio->bi_iter.bi_sector << 9;
char *buffer;
struct bio_vec bvec;
struct bvec_iter iter;
int ret=0;
bio_for_each_segment(bvec,bio,iter){
if(((iter.bi_sector << 9) + iter.bi_size ) > MY_BLOCK_CAPACITY ){
printk("Beyond-end write (%ld %d)\n",offset,iter.bi_size);
ret=-EIO;
return ret;
}
buffer=kmap(bvec.bv_page) + bvec.bv_offset;
if(bio_data_dir(bio)==READ){
memcpy(buffer,blodev_data+offset,bvec.bv_len);
flush_dcache_page(bvec.bv_page);
}else if(bio_data_dir(bio)==WRITE){
flush_dcache_page(bvec.bv_page);
memcpy(blodev_data+offset,buffer,bvec.bv_len);
}else{
kunmap(bvec.bv_page);
goto out;
}
kunmap(bvec.bv_page);
offset += bvec.bv_len;
}
out:
bio_endio(bio);
return 0;
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("guoz");
MODULE_DESCRIPTION("this is a ostest demo2");
如上所示,定义一个全局的块设备
static struct gendisk *blodev;
为这个块设备申请资源、配置、加入系统
blodev = alloc_disk(1);
strcpy(blodev->disk_name,MY_BLOCK_NAME);
blodev->major=MY_DEVICE_MAJOR;
blodev->first_minor=0;
blodev->fops=&blodev_fops;
blodev->queue=blodev_queue;
set_capacity(blodev,MY_BLOCK_CAPACITY>>9);
add_disk(blodev);
到此整个驱动的结构已经出来,剩下的便是添加配置块设备中所需要的函数和数据,配置块设备的代码有六行,下面逐行进行填充。
第一行:需要设置块设备的名称,添加宏定义:
#define MY_BLOCK_NAME “demo2_blodev”
第二三行:设置主从驱动号,这里使用的是操作系统已存在的驱动号,添加宏定义:
#define MY_DEVICE_MAJOR COMPAQ_SMART2_MAJOR
第四行:设置块设备的文件操作函数,添加一个结构体:
struct block_device_operations blodev_fops={
.owner=THIS_MODULE
};
第五行:设置io请求队列,请求队列的处理函数便是这个驱动的核心内容,这里采用的无队列和全局变量存储的方式。
static struct request_queue *blodev_queue; //定义请求队列
unsigned char blodev_data[MY_BLOCK_CAPACITY]; //全局数据存储数组
//io请求的处理函数
static unsigned int do_request(struct request_queue q,struct bio * bio);
//队列资源申请
blodev_queue=blk_alloc_queue(GFP_KERNEL);
//请求队列和对应的处理函数绑定
blk_queue_make_request(blodev_queue,do_request);
第六行,设置块设备的磁盘容量,添加宏定义:
#define MY_BLOCK_CAPACITY (5121024) //512k的磁盘空间
最后添加驱动模块的必要部分,就完成了整个驱动的编写。
makefile文件:
ifneq ($(KERNELRELEASE),)
obj-m := demo2.o
else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -fr .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.*
endif
运行命令make编译,sudo insmod demo2.ko加载内核模块并且dmesg显示设备号:
3.闹钟实现
工作目录下建立clock.c文件:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
static void sig_alrm(int signo);
int main(void)
{
signal(SIGALRM,sig_alrm);
system("date");
alarm(20);
sleep(5);
printf("%d\n",alarm(15));
pause();
}
static void sig_alrm(int signo){
system("date");
return;
}
```如图所示,先设置了一个闹钟函数alarm(20),即在20秒时将SIGALRM信号传送送给当前进程;然后又定义了一个延时函数sleep(5),接着又定义了一个闹钟函数alarm(15),它的作用是清除前面设置的闹钟alarm(20)并返回剩余的时间20-5=15秒。所以,程序先执行system("date")语句输出当前时间;然后进程休眠5秒后,程序执行输出语句printf("%d\n",alarm(15)),由于alarm(15)先返回15秒,即打印输出15;接着程序执行pause()函数,使当前进程处于挂起状态,直到捕捉到一个信号;当再过15秒后,SIGALARM信号的处理函数sig_alrm执行system("date")语句输出当前时间;最后pause终止进程。因此,整个程序执行的时间为5+15=20秒。
执行命令gcc clock.c -o test编译生成可执行文件,然后运行命令./test执行程序。
运行结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210508185006520.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzcyNTUwMQ==,size_16,color_FFFFFF,t_70)
统计进程运行时间
工作目录下建立文件timeCount.cpp:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
int main()
{
int i=10;
clock_t start,finish;
double Times;
start=clock();
while(i--){
cout<<i<<endl;
};
finish=clock();
Times=(double)(finish-start)/CLOCKS_PER_SEC;
cout<<"start(ticks): "<<start<<endl;
cout<<"finish(ticks): "<<finish<<endl;
cout<<"CLOCKS_PER_SEC: "<<CLOCKS_PER_SEC<<endl;
cout<<"Total Times(s)(CLOCKS_PER_SEC): "<<Times<<endl;
return 0;
}
执行命令g++ timeCount.cpp -o test编译生成可执行文件test,运行./test
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210508185049602.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzcyNTUwMQ==,size_16,color_FFFFFF,t_70)