1. Linux 动态模块
  2. Linux 设备驱动
  3. 字符驱动程序的实现
  4. 附录

Github 项目地址

1. LInux 动态模块

  1. Linux 动态模块概述
  2. 动态模块的结构
  3. 动态模块的实现细节

1.1 Linux 动态模块概述

与普通用户代码区别

  1. 动态模块无法使用 C 标准函数库,只能使用内核函数
  2. 内核模块的栈大小有限制(一般只有 4kb)
  3. 内核模块是一个由多个回调函数组成的”被动”代码集合体,采用了”事件驱动模型”

构建与使用流程

  1. 创建动态模块源码
  2. 修改 Makefile 文件生成编译规则
  3. 编译创建的模块源码,生成驱动模块
  4. 安装驱动模块
  5. 查看是否安装成功
  6. 使用驱动模块
  7. 卸载驱动模块

shell 命令

命令 作用
insmod 加载动态模块
lsmod 显示已加载模块
rmmod 卸载动态模块

1.2 动态模块的结构

源代码格式

1
2
3
4
5
6
7
8
9
10
11
#include <linux/init.h>
#include <linux/kernel.h> // 说明是个内核功能
#include <linux/module.h> // 说明是个模块
int init_module(){ //初始化模块
return 0;
}
void cleanup_module(){ //卸载模块

module_init(init_module); //注册初始化函数
module_exit(mymodule_exit); //注册清除函数
MODULE_LICENSE("GPL"); //模块许可声明

Makefile 格式

1
2
3
4
5
6
7
8
ifneq ($(KERNELRELEASE),)
obj-m := mymodules.o #obj-m指编译成外部模块
else
KERNELDIR := /lib/modules/$(shell uname -r)/build #定义一个变量,指向内核目录
PWD := $(shell pwd)
 modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules #编译内核模块
 endif

1.3 动态模块实现的细节

曾经用来测试系统调用的动态动态模块
为了不影响阅读放在了文件最后面

2. Linux 设备驱动

  1. Linux 设备驱动概述(PPT)
  2. Linux 设备驱动概述(博客总结)

2.1 Linux 设备驱动概述(PPT)

特点

  1. 为内核和其它子系统提供一个标准的接口
  2. 使用一些标准的内核服务,如内存服务

驱动程序与外界的接口

  1. 与内核的接口:通过数据结构 file_operations 来完成
  2. 与系统引导的接口:利用驱动程序对设备进行初始化
  3. 与设备的接口:描述驱动程序如何与设备进行交互

设备文件的属性

  1. 主设备号:
    指名唯一的设备类型,即表示设备对应的驱动程序类型,它是块设备表和字符设备表中表项的索引。大多情况下,一个主设备号对应一个设备驱动程序
  2. 次设备号:
    用于在一组主设备号相同的设备之间唯一标识特定的设备,如两个相同的硬件就可用次设备号予以区分

代码结构

设备驱动程序是一个模块,内核用 file 结构标识设备驱动程序,内核使用 file_operations 结构来访问驱动程序中的函数
代码组成:

驱动程序的注册与注销
设备的打开与释放
设备的读写操作
设备的控制操作
设备的中断和轮询处理

2.2 Linux 设备驱动概述(博客总结)

参考的博客:Linux 设备驱动之字符设备驱动

定义
只能一个字节一个字节地进行操作的设备

定义操作
引用自 CSDN 博主「andylauren」的原创文章: 原文链接

  如上图,在 linux 内核中使用 cdev 结构体来描述字符设备,通过其成员 dev_t 来定义设备号,以确定字符设备的唯一性。通过其成员 file_operations 来定义字符设备驱动提供给 VFS 的接口函数,如常见的 open()、read()、write()等。

  在 Linux 字符设备驱动中,模块加载函数通过 register_chrdev_region( ) 或 alloc_chrdev_region( )来静态或者动态获取设备号,通过 cdev_init( )建立 cdev 与 file_operations 之间的连接,通过 cdev_add( )向系统添加一个 cdev 以完成注册。模块卸载函数通过 cdev_del( )来注销 cdev,通过 unregister_chrdev_region( )来释放设备号。

  用户空间访问该设备的程序通过 Linux 系统调用,如 open( )、read( )、write( ),来“调用”file_operations 来定义字符设备驱动提供给 VFS 的接口函数。

3. 字符驱动程序的实现

  1. 实验内容
  2. 实验分析
  3. 实验源代码

3.1 实验内容

编写一个简单的字符设备驱动程序
以内核空间模拟字符设备
完成对该设备的打开,读写和释放操作
编写聊天程序实现对该设备的同步和互斥操作

3.2 实验分析

  1. 驱动模块分析
  2. 聊天客户端分析

3.2.1 驱动模块分析

============字符驱动模块开始启动========

  1. 实现相关操作函数:open, read, write, close, ioctl
  2. 申请并分配设备号
  3. 创建设备节点(node 文件)
  4. 初始化 cdev(绑定 file_operations 与操作函数的具体实现)
  5. 注册 cdev
    ============字符驱动模块完成启动========
    ============字符驱动模块开始卸载========
  6. 注销 cdev
  7. 删除设备节点(node 文件)
  8. 释放设备号
    ============字符驱动设备完成卸载========

3.2.2 聊天客户端分析

总体分析

  1. 每个客户端由两个子线程组成—读线程,写线程
  2. 每个客户端总是以一定频率尝试读取最新的消息,如果发现消息与原来的信息相同则舍弃不更新,否则更新消息
  3. 每个客户端在写的时候无法读取最新的消息
  4. 每个客户端发送的消息可以被所有客户端收到

具体实现

  1. 每个客户端可以看成是一个读者或者一个写者
  2. 正常情况下客户端以一定频率发送读取请求
  3. 客户端由 ctrl+\触发写者模式,读取用户输入并发送写入请求
  4. 由于客户端总是不断发送读取请求,所以总体模型应该是写者优先的

尝试的方法

刚开始尝试使用自旋锁进行同步操作,结果实在是太消耗资源了…(三个客户端进程就让整个电脑卡得不行不行的)
又尝试使用检测键盘按下来触发写者模式,结果既消耗资源又效果不佳(输入的时候乱七八糟的的)

遇到的问题

  1. sscanf 函数使用前需要清空缓冲区
  2. printf 函数最后需要输出\n 才能刷新输出缓冲区
  3. 为了实现进程同步,不能单纯地使用信号量机制,而需要信号量机制与共享内存共同配合

最终解决方案

  1. 采用单线程,客户端有两个模式——写者模式/读者模式
  2. 创建客户端的时候可以传入参数用来初始化同步变量
  3. 使用共享内存和信号量的方案来解决进程间的同步问题
  4. (共享内存可以进行进程间通信,信号量可以用来解决线程间同步)

3.3 实验源代码

  1. 驱动模块
  2. 客户端模块

3.3.1 驱动模块

  1. 源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    //=========================
    // author: bibibi
    // description: 字符设备驱动模块
    //=========================
    #include <linux/module.h>
    #include <linux/types.h>
    #include <linux/fs.h>
    #include <linux/errno.h>
    #include <linux/mm.h>
    #include <linux/sched.h>
    #include <linux/init.h>
    #include <linux/cdev.h>
    #include <asm/io.h>
    #include <linux/slab.h>
    #include <linux/uaccess.h>
    #include <linux/device.h>


    // > ============字符驱动模块开始启动========
    // > 1. 实现相关操作函数:open, read, write, close, ioctl
    // > 2. 申请并分配设备号
    // > 3. 创建设备节点(node文件)
    // > 4. 初始化cdev(绑定file_operations与操作函数的具体实现)
    // > 5. 注册cdev
    // > ============字符驱动模块完成启动========
    // > ============字符驱动模块开始卸载========
    // > 6. 注销cdev
    // > 7. 删除设备节点(node文件)
    // > 8. 释放设备号
    // > ============字符驱动设备完成卸载========

    //描述设备的结构体
    // struct cdev {
    // struct kobject kobj; 每个 cdev 都是一个 kobject
    // struct module *owner; 指向实现驱动的模块
    // const struct file_operations *ops; 操纵这个字符设备文件的方法
    // struct list_head list; 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
    // dev_t dev; 起始设备编号
    // unsigned int count; 设备范围号大小
    // };


    //=======================宏定义与功能函数声明===================

    //定义设备号,如果定义为0,则动态申请设备号
    #ifndef BIBIBI_DEV_MAJOR
    #define BIBIBI_DEV_MAJOR 333
    #endif

    //定义子设备的数量
    #ifndef BIBIBI_DEV_NUM
    #define BIBIBI_DEV_NUM 2
    #endif

    //定义字符设备的缓冲区的大小
    #ifndef BIBIBI_DEV_SIZE
    #define BIBIBI_DEV_SIZE 1024
    #endif

    //相关操作函数声明
    static int bibibi_open(struct inode *inode, struct file *filp); //打开字符设备
    static ssize_t bibibi_read(struct file *filp, char __user *buf, size_t size, loff_t *pos); //读取字符设备(一个字节)
    static ssize_t bibibi_write(struct file *filp, const char __user *buf, size_t size, loff_t *pos); //写入字符设备(一个字节)
    static loff_t bibibi_llseek(struct file *filp, loff_t offset, int whence); //控制指针位置
    static int bibibi_release(struct inode *inode, struct file *filp); //关闭字符设备
    static int bibibi_ioctl(int fd, int cmd); //控制字符设备


    //=======================相关的数据结构定义===================
    //驱动子设备的结构体
    struct bibibi_device{
    char *data; //缓冲区指针
    unsigned long size; //缓冲区大小变量
    };

    //驱动子设备结构体数组的指针
    struct bibibi_device *bibibi_devices;

    //设备节点文件指针
    struct class *bibibi_class;

    //驱动模块的cdev
    struct cdev bibibi_cdev;

    //主设备号
    static int bibibi_major=BIBIBI_DEV_MAJOR;
    //设定模块向内核传递的参数
    //可以通过这一个参数来在加载模块的时候决定设备号,默认设备号为333
    module_param(bibibi_major,int, S_IRUGO);

    //模块操作定义结构体
    static const struct file_operations bibibi_operations={
    .owner=THIS_MODULE,
    .llseek=bibibi_llseek,
    .read=bibibi_read,
    .write=bibibi_write,
    .open=bibibi_open,
    //.ioctl=bibibi_ioctl, 报错说没有这一个成员,先注释掉
    .release=bibibi_release,
    };



    //初始化字符驱动模块
    static int __init init_char_driver(void){
    //2. 申请并分配设备号
    //如果指定了设备号,采用静态分配设备号
    //如果没有指定,采用动态分配设备号

    int result=0; //用来接收一些函数的返回值
    dev_t device_num=MKDEV(bibibi_major, 0); //将主次设备号合成一个设备号


    //申请设备号
    if(bibibi_major){
    //静态申请
    result=register_chrdev_region(device_num, BIBIBI_DEV_NUM, "bibibi_char_dev");
    }
    else{
    //动态申请
    result = alloc_chrdev_region(&device_num, 0, BIBIBI_DEV_NUM, "bibibi_char_dev");
    bibibi_major = MAJOR(device_num);
    }

    //申请设备号失败
    if(result<0) return result;


    //3. 创建设备节点(node文件)
    //静态申请BIBIBI_DEV_NUM个节点 标志为"进程上下文,可以睡眠"
    bibibi_devices=kmalloc(BIBIBI_DEV_NUM * sizeof(struct bibibi_device), GFP_KERNEL);
    if(!bibibi_devices){
    //创建失败,释放 设备号并返回
    result = -ENOMEM;
    unregister_chrdev_region(device_num, BIBIBI_DEV_NUM);
    return result;
    }

    // //创建设备文件
    // bibibi_class = class_create(THIS_MODULE, "bibibi_class");

    // printk("error type: %d",IS_ERR(bibibi_class));
    // result = -ENOMEM;
    // unregister_chrdev_region(device_num, BIBIBI_DEV_NUM);
    // return result;

    // if(IS_ERR(bibibi_class)){
    // printk("Err: failed in creating class.\n");
    // return -1;
    // }
    // device_create(bibibi_class, NULL, MKDEV(bibibi_major, 0), "bibibi_device",0);



    //填充已经申请的内存空间为0
    //void * memset (void *s ,int c, size_t n);
    memset(bibibi_devices,0,BIBIBI_DEV_NUM * sizeof(struct bibibi_device));

    int i=0;
    for(i=0;i<BIBIBI_DEV_NUM;i++){
    //赋值缓冲区长度变量并填充空间 标志为"进程上下文,可以睡眠"
    bibibi_devices[i].size=BIBIBI_DEV_SIZE;
    bibibi_devices[i].data=kmalloc(BIBIBI_DEV_SIZE,GFP_KERNEL );
    memset(bibibi_devices[i].data,0,BIBIBI_DEV_SIZE);
    }


    //4. 初始化cdev(绑定file_operations与操作函数的具体实现)
    cdev_init(&bibibi_cdev,&bibibi_operations);
    bibibi_cdev.owner=THIS_MODULE;
    bibibi_cdev.ops=&bibibi_operations;

    //5. 注册cdev
    cdev_add(&bibibi_cdev,MKDEV(bibibi_major, 0),BIBIBI_DEV_NUM);


    //驱动模块启动成功
    printk("device init success\n");
    return 0;
    }


    //清理工作
    static void __exit exit_char_driver(void){
    // > 6. 注销cdev
    // > 7. 删除设备节点(node文件)
    // > 8. 释放设备号

    //6. 注销cdev
    cdev_del(&bibibi_cdev);

    //7. 删除设备节点
    kfree(bibibi_devices);
    //删除磁盘设备文件
    // device_destroy(bibibi_class, MKDEV(bibibi_major, 0));
    // class_destroy(bibibi_class);

    //8. 释放设备号
    unregister_chrdev_region(MKDEV(bibibi_major, 0), 2);

    //驱动模块卸载完毕
    printk(KERN_INFO "device exit success");
    }


    //相关参数的具体操作
    // static int bibibi_open(struct inode *inode, struct file *filp); //打开字符设备
    // static ssize_t bibibi_read(struct file *filp, char __user *buf, size_t size, loff_t *pos); //读取字符设备(一个字节)
    // static ssize_t bibibi_write(struct file *filp, const char__user *buf, size_t size, loff_t *pos); //写入字符设备(一个字节)
    // static loff_t bibibi_llseek(struct file *filp, loff_t offset, int whence); //控制指针位置
    // static int bibibi_release(struct inode *inode, struct file *filp); //关闭字符设备
    // static int bibibi_ioctl(int fd, int cmd); //控制字符设备

    static int bibibi_open(struct inode *inode, struct file *filp){
    struct bibibi_device *device;

    //判断是否已经达到了支持的子设备的上限
    int num=MINOR(inode->i_rdev);
    if(num>=BIBIBI_DEV_NUM)
    return -ENODEV;

    //分配设备
    //device=&bibibi_devices[num];
    //为了实现聊天室,不同的设备请求返回同一个子设备
    //互斥和同步由客户端完成
    device=&bibibi_devices[0];
    filp->private_data=device;

    return 0;
    }

    static ssize_t bibibi_read(struct file *filp, char __user *buf, size_t size, loff_t *pos){
    unsigned long position=*pos;
    unsigned int count=size;
    int result;
    struct bibibi_device *device=filp->private_data;

    //如果要求位置非法则直接退出
    if(position>=BIBIBI_DEV_SIZE)
    return 0;
    //调整读取数据长度
    if(count>BIBIBI_DEV_SIZE-position)
    count=BIBIBI_DEV_SIZE-position;

    //传送数据给用户
    if(copy_to_user(buf, (void*)(device->data + position), count)) {
    result = -EFAULT;
    } else {
    *pos += count;
    result = count;
    printk(KERN_INFO "read %d bytes from %lu\n", count, position);
    }

    return result;
    }

    static ssize_t bibibi_write(struct file *filp, const char __user *buf, size_t size, loff_t *pos){
    unsigned long position = *pos;
    unsigned int count = size;
    int result = 0;
    struct bibibi_device *device = filp->private_data;

    //确保写入安全
    if (position >= BIBIBI_DEV_SIZE)
    return 0;
    if (count > BIBIBI_DEV_SIZE-position)
    count = BIBIBI_DEV_SIZE - position;

    //写入数据
    if (copy_from_user(device->data + position, buf, count)) {
    result = -EFAULT;
    } else {
    *pos += count;
    result = count;
    printk(KERN_INFO "write %d bytes from %lu\n", count, position);
    }

    return result;
    }

    static loff_t bibibi_llseek(struct file *filp, loff_t offset, int whence){
    loff_t newpos;

    //选择操作类型
    switch (whence) {
    //指定新位置
    case 0:
    newpos = offset;
    break;
    //后移指针
    case 1:
    newpos = filp->f_pos + offset;
    break;
    //反向指定新位置
    case 2:
    newpos = BIBIBI_DEV_SIZE - 1 + offset;
    break;
    default:
    return -EINVAL;
    }
    if ((newpos < 0) || (newpos > BIBIBI_DEV_SIZE))
    return -EINVAL;

    filp->f_pos = newpos;
    return newpos;
    }

    static int bibibi_release(struct inode *inode, struct file *filp){
    //释放子设备操作
    //其实没什么需要做的
    return 0;
    }

    static int bibibi_ioctl(int fd, int cmd){
    //暂时不支持操纵设备
    return 0;
    }

    //=======================驱动模块声明==========================

    module_init(init_char_driver);
    module_exit(exit_char_driver);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("yishiyu");

  2. Makefile

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    obj-m := char_driver.o
    KERNELDIR := /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    CONFIG_MODULE_SIG=n

    modules:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules


    clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
  3. 部署与退出脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #运行脚本
    #!/bin/bash

    sudo make

    sudo insmod char_driver.ko bibibi_major=181
    sudo mknod /dev/bibibi_device c 181 0

    #mknod 创建特殊文件 c表示面向字符
    #mknod Name { b | c } Major Minor

    #删除设备文件可以直接使用rm -f 设备文件名
    #rm -f memdev0

    #退出脚本
    #!/bin/bash

    sudo rmmod char_driver
    sudo rm -f /dev/bibibi_device

    3.3.2 客户端模块

  4. 源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    //=========================
    // author: bibibi
    // description: 使用字符设备通信
    //=========================

    //使用bibibi_device字符设备
    //实现一个可以实现一对多通信的客户端
    //每个客户端地位相等,每个客户端开启时需要进行一个命名
    //每个客户端发送一个信息的时候其他所有客户端都会收到信息
    //使用写者读者模型,写者优先
    //
    //每个客户端可以执行两个操作——读或者写
    //每次写操作将覆盖设备全部的缓冲区
    //
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<fcntl.h>
    #include <pthread.h>
    #include <sys/types.h>
    #include <sys/un.h>
    #include <signal.h>
    #include <semaphore.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>

    #define MAX_BUFFER 64
    #define WRITER 0
    #define READER 1
    #define SHARED_MEM 111

    //用户名变量(暂时不确保所有用户不重名)
    static char my_name[16];
    static char buf[MAX_BUFFER]={0};
    static char prebuf[MAX_BUFFER]={0};
    static char exit_signal[]="quit";
    static int fd=0;

    //更改客户端模式
    static int running=1;
    static int mode=READER;
    void change_mode();
    void client_write();
    void client_read();

    // wmutex:semaphore=1 //读者与写者之间、写者与写者之间互斥使用共享数据
    // S:semaphore=1 //当至少有一个写者准备访问共享数据时,它可使后续的读者等待写完成
    // S2:semaphore=1 //阻塞第二个以后的等待读者
    // Readcount,writecount: int = 0,0; //当前读者数量、写者数量
    // mutex1 :semaphore = 1 //多个读者互斥使用 readcount
    // mutex2 :semaphore = 1 //多个写者互斥使用 writecount

    //同步信号量
    struct client_syn{
    sem_t data_available; //操作权限 相当于wmutex
    sem_t writer_syn; //写者操作时的阻塞信号量 相当于S
    sem_t reader_syn; //读者操作时的阻塞信号量 相当于S2
    int reader_count; //读者计数器
    int writer_count; //写者计数器
    sem_t reader_count_en; //读者计数器操作信号量
    sem_t writer_count_en; //读者计数器操作信号量
    }client_syn;
    static struct client_syn *syn_var;

    //单进程客户端
    int main(char argc,char *argv[]){

    signal(SIGQUIT , change_mode); //设置键盘中断

    //设置公共信号量
    int shared_mem = shmget((key_t)SHARED_MEM, sizeof(client_syn), 0666 | IPC_CREAT);
    void *shm = shmat(shared_mem, 0, 0);
    syn_var=(struct client_syn*)shm;

    //如果有参数则初始化公共信号量
    if(argc>1){
    //每次只能有一个进程进行操作
    sem_init(&(syn_var->data_available), 1, 1);
    sem_init(&(syn_var->writer_syn), 1, 1);
    sem_init(&(syn_var->reader_syn), 1, 1);
    sem_init(&(syn_var->reader_count_en), 1, 1);
    sem_init(&(syn_var->writer_count_en), 1, 1);
    //初始化chengggong
    printf("init success!");
    }

    //获取用户名称
    printf("please input your name:\t");
    // scanf("%10[a-z]",&my_name);

    char temp[3]=":";
    scanf("%10[a-z]",my_name);
    strncat(&my_name,temp,2);
    //打开文件
    fd = open("/dev/bibibi_device",O_RDWR);
    if (fd == -1) {
    printf("open bibibi_device failed!\n");
    return -1;
    }

    printf("====================client %s start================\n",my_name);

    while(running){
    sleep(1); //更新频率为1Hz
    switch (mode){
    case WRITER:
    client_write();
    break;
    case READER:
    client_read();
    break;
    default:
    break;
    }
    }

    printf("====================client %s exit================\n",my_name);

    //清理工作


    return 0;
    }

    //切换模式
    void change_mode(){
    mode=1-mode;
    printf("\b\bplease type in\n");
    }


    //写入函数
    void client_write(){

    // P(mutex2); // 申请访问 writecount
    // if writecount=0 then P(S); // 写者进程进入等待队列
    // writecount++;
    // V(mutex2); // 释放 writecount
    // P(wmutex); // 是否有读者或者写者正在操作
    // writing …
    // V(wmutex); // 写操作完成
    // P(mutex2);
    // writecount--;
    // if writecount=0 then V(S); // 没有写者在等待,允许读者进程进入操作
    // V(mutex2);

    // sem_t data_available; //操作权限 相当于wmutex
    // sem_t writer_syn; //写者操作时的阻塞信号量 相当于S
    // sem_t reader_syn; //读者操作时的阻塞信号量 相当于S2
    // int reader_count; //读者计数器
    // int writer_count; //写者计数器
    // sem_t reader_count_en; //读者计数器操作信号量
    // sem_t writer_count_en; //读者计数器操作信号量

    //同步操作
    sem_wait(&(syn_var->writer_count_en));
    if(syn_var->writer_count==0) sem_wait(&(syn_var->writer_syn)); //阻塞读者操作
    syn_var->writer_count++;
    sem_post(&(syn_var->writer_count_en));
    sem_wait(&(syn_var->data_available));


    //读取输入
    printf("%s",my_name);
    setbuf(stdin,NULL);
    scanf("%50[a-z]",buf);

    if(strcmp(buf,exit_signal)==0){
    printf("client quit\n");
    running=0;
    }

    write(fd,my_name,strlen(my_name));
    lseek(fd, strlen(my_name), SEEK_SET);
    write(fd,buf, sizeof(buf));
    lseek(fd, 0, SEEK_SET);

    read(fd, prebuf, sizeof(prebuf));
    lseek(fd, 0, SEEK_SET);
    mode=READER;


    sem_post(&(syn_var->data_available));
    sem_wait(&(syn_var->writer_count_en));
    syn_var->writer_count--;
    if(syn_var->writer_count==0) sem_post(&(syn_var->writer_syn)); //释放读者操作
    sem_post(&(syn_var->writer_count_en));
    }

    //读取函数
    void client_read(){

    // P(S2); // 是否已经有读者进程在等待队列中
    // P(S); // 是否有写者进程在等待队列中
    // P(mutex1); // 没有其他的读者进程在访问 readcount
    // if readcount=0 then P(wmutex); // 判断是否有写者进程在写
    // readcount++;
    // V(mutex1); // 释放 readcount
    // V(S); // 允许写者进程等待
    // V(S2); // 允许读者进程等待
    // reading …
    // P(mutex1);
    // readcount--;
    // if readcount=0 then V(wmutex); // 允许写者进程写
    // V(mutex1); // 释放 readcount

    // sem_t data_available; //操作权限 相当于wmutex
    // sem_t writer_syn; //写者操作时的阻塞信号量 相当于S
    // sem_t reader_syn; //读者操作时的阻塞信号量 相当于S2
    // int reader_count; //读者计数器
    // int writer_count; //写者计数器
    // sem_t reader_count_en; //读者计数器操作信号量
    // sem_t writer_count_en; //读者计数器操作信号量


    //同步操作
    sem_wait(&(syn_var->reader_syn));
    sem_wait(&(syn_var->writer_syn));
    sem_wait(&(syn_var->reader_count_en));
    if(syn_var->reader_count==0) sem_wait(&(syn_var->data_available)); //读的时候禁止写入
    syn_var->reader_count++;
    sem_post(&(syn_var->reader_count_en));
    sem_post(&(syn_var->writer_syn));
    sem_post(&(syn_var->reader_syn));

    //读取文件
    read(fd, buf, sizeof(buf));
    lseek(fd, 0, SEEK_SET);

    //如果两次内容不完全一致则输出
    if(strcmp(buf,prebuf)!=0){
    printf("%s\n",buf);
    strncpy(prebuf,buf,MAX_BUFFER);
    }

    sem_wait(&(syn_var->reader_count_en));
    syn_var->reader_count--;
    if(syn_var->reader_count==0) sem_post(&(syn_var->data_available)); //释放写权限
    sem_post(&(syn_var->reader_count_en));
    }
  5. Makefile

    1
    2
    3
    4
    5
    all:
    gcc client.c -o client.o -g -lpthread

    clean:
    rm client.o
  6. 部署脚本

    1
    2
    3
    4
    #!/bin/bash

    sudo make
    sudo ./client.o

    4. 附录

  1. 用来测试系统调用的动态模块

4.1 用来测试系统调用的动态模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/unistd.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <asm/fcntl.h>
#include <linux/string.h>


//定义系统调用号和系统调用表地址
#define my_syscall_num 279
#define sys_call_table_adress 0xffffffff81a001c0

unsigned int clear_and_return_cr0(void);
void setback_cr0(unsigned int val);
asmlinkage long sys_mycall(
char *user_cpu,char * user_version,char *user_memory, char *user_process,int user_processID);
// int len_cpu, int len_version,int len_memory, int len_process);
int orig_cr0;
unsigned long *sys_call_table = 0;
static int (*anything_saved)(void);

//修改cr0超级写权限
unsigned int clear_and_return_cr0(void)
{
unsigned int cr0 = 0;
unsigned int ret;

asm("movq %%cr0, %%rax"
: "=a"(cr0));
ret = cr0;

cr0 &= 0xfffffffffffeffff;
asm("movq %%rax, %%cr0" ::"a"(cr0));

//修改前:0x80050033
//修改后:0x80040032
//16位为写保护位
//0x00010000
//最低位改变=>无问题
//说明不是因为写保护位的原因导致无法无法读取内存

return ret;
}

//复位cr0寄存器
void setback_cr0(unsigned int val) //读取val的值到eax寄存器,再将eax寄存器的值放入cr0中
{
asm volatile("movq %%rax, %%cr0" ::"a"(val));
}


//初始化
static int __init init_addsyscall(void)
{
sys_call_table = (unsigned long *)sys_call_table_adress; //获取系统调用服务首地址
anything_saved = (int (*)(void))(sys_call_table[my_syscall_num]); //保存原始系统调用的地址
orig_cr0 = clear_and_return_cr0(); //设置cr0可更改
sys_call_table[my_syscall_num] = (unsigned long )(&sys_mycall); //更改原始的系统调用服务地址
setback_cr0(orig_cr0); //设置为原始的只读cr0
return 0;
}


//系统调用的实现
//CPU型号
//操作系统的版本号
//系统中的进程等类似于Windows的任务管理器的信息
asmlinkage long sys_mycall(
char *user_cpu, char * user_version, char *user_memory, char *user_process, int user_processID)
// int len_cpu, int len_version,int len_memory, int len_process);
{
struct file *file_pointer; //文件结构体指针
mm_segment_t file_segment; //内存环境设置变量
loff_t pos; //读写指针
char buffer[1000]; //缓冲区
int len_buffer=sizeof(buffer)-1; //缓冲区大小-1
int len_read=len_buffer; //目标读取数
ssize_t byte_read=0; //实际读取数,用来清空缓冲区

//将内存设置为内核空间并保存原本内存空间
file_segment=get_fs();
set_fs(KERNEL_DS);


//读取CPU信息
file_pointer=filp_open("/proc/cpuinfo",O_RDONLY,0); //打开文件
pos=0; //设置读开始位置
//len_read=(len_buffer>len_cpu)?len_cpu:len_buffer; //计算合法的最大读取数
byte_read = vfs_read(file_pointer,buffer,len_read,&pos); //将数据读入缓冲区
buffer[byte_read]=0; //保证在正确的位置以0结束
copy_to_user(user_cpu,buffer,sizeof(buffer)+1); //将输入送往用户空间
filp_close(file_pointer,NULL); //cpu信息读取结束 关闭文件

//读取Version信息
file_pointer=filp_open("/proc/version",O_RDONLY,0); //打开文件
pos=0; //设置读开始位置
//len_read=(len_buffer>len_version)?len_version:len_buffer; //计算合法的最大读取数
byte_read = vfs_read(file_pointer,buffer,len_read,&pos); //将数据读入缓冲区
buffer[byte_read]=0; //保证在正确的位置以0结束
copy_to_user(user_version,buffer,sizeof(buffer)); //将输入送往用户空间
filp_close(file_pointer,NULL); //version信息读取结束 关闭文件

//读取Memory信息
file_pointer=filp_open("/proc/meminfo",O_RDONLY,0); //打开文件
pos=0; //设置读开始位置
//len_read=(len_buffer>len_memory)?len_memory:len_buffer; //计算合法的最大读取数
byte_read = vfs_read(file_pointer,buffer,len_read,&pos); //将数据读入缓冲区
buffer[byte_read]=0; //保证在正确的位置以0结束
copy_to_user(user_memory,buffer,sizeof(buffer)); //将输入送往用户空间
filp_close(file_pointer,NULL); //memory信息读取结束 关闭文件

//读取进程信息
char process_dir[30]="/proc/";
char temp_char[10]="/status";
char temp_ID[10]="";
int index=0,temp_num=user_processID;
for(index=0;temp_num>9;index++,temp_num/=10);
while(index>=0){
temp_ID[index]=(user_processID%10)+48;
index--;
user_processID/=10;
}
strcat(process_dir,temp_ID);
strcat(process_dir,temp_char);

//char final[]="/proc/20/status";

file_pointer=filp_open( process_dir,O_RDONLY,0); //打开文件
file_pointer=filp_open( process_dir,O_RDONLY,0); //打开文件
pos=0; //设置读开始位置
//len_read=(len_buffer>len_process)?len_process:len_buffer; //计算合法的最大读取数
byte_read = vfs_read(file_pointer,buffer,len_read,&pos); //将数据读入缓冲区
buffer[byte_read]=0; //保证在正确的位置以0结束
//copy_to_user(user_process,process_dir,sizeof(buffer)); //将输入送往用户空间
copy_to_user(user_process,buffer,sizeof(buffer)); //将输入送往用户空间
filp_close(file_pointer,NULL); //memory信息读取结束 关闭文件


//运行结束前将内存设置回用户空间
set_fs(file_segment);

return current->pid;
}


//清理工作
static void __exit exit_addsyscall(void)
{
orig_cr0 = clear_and_return_cr0(); //设置cr0可更改
sys_call_table[my_syscall_num] = (unsigned long)anything_saved;
setback_cr0(orig_cr0);
printk("module unloaded\n");
}

module_init(init_addsyscall);
module_exit(exit_addsyscall);
MODULE_LICENSE("Dual BSD/GPL");