首页 > 系统相关 >Linux内核驱动编程的一道陷阱题(转载)

Linux内核驱动编程的一道陷阱题(转载)

时间:2024-03-29 23:35:27浏览次数:28  
标签:kernel 31 编程 localhost 内核 Linux test loop 255

本篇转载于:https://blog.csdn.net/yhb1047818384/article/details/84073838   原文如下: ------ 看过一道linux内核驱动编程的题目,我觉得有点价值。 题目很简单,凭记忆整理了下,代码如下:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/kthread.h>

struct task_struct *task = NULL;
int x = 0;
int y = 0;

static int test_loop(void *data)
{
        unsigned long time_end = jiffies;
        while (y == 0) {
                if (time_after(jiffies, time_end  + 10 *HZ)) {
                        printk("break out!\n");
                        break;
                }
        }

        if (y) {
                printk("test_loop : x=%d y=%d\n", x, y);
        }
        return 0;
}

static int __init test_init(void)
{
        task = kthread_create(test_loop, 0,"test_loop");
        wake_up_process(task);
        msleep(100);
        x = 255;
        y = 1;
        printk("x=%d y=%d\n", x, y);
        kthread_stop(task);
        task = NULL;
        return 0;
}

static void __exit test_exit(void)
{

}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
如果有兴趣可以在自己的linux环境上操作下,看下输出结果。 Makefile如下:
obj-m := test.o
KERNEL_DIR := /lib/modules/$(shell uname -r)/build

PWD := $(shell pwd)
all:

        make -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules
clean:  

        rm *.o *.ko *.mod.c

.PHONY:clean
驱动代码很简洁,只有一个内核线程和2个全局变量,全局变量在内核线程唤醒后才会改变。 其实这个题目有2处问题。 我们先尝试编译运行一下,结果输出:
[ 5886.273365] x=255 y=1
[ 5896.174020] break out!
[ 5896.174023] test_loop : x=255 y=1
这里打印了“break out”, 且2行输出之间间隔了10s. 这是一个并发编程很容易碰到的坑,不管是内核驱动代码还是应用层代码,都有可能碰到。 为了方便定位,使用应用层代码复现一下:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

int y = 0;
int x = 0;

static void *test_loop(void *data)
{
        while (y == 0 ){

        }

        if (y) {

                printf("test_loop: x=%d y=%d\n", x, y);
        }
}

int main(int argc, char *argv)
{
        pthread_t t1;
        int ret = 0;
        ret = pthread_create(&t1, NULL, &test_loop, 0);
        if (ret) {
                printf("create pthread error!\n");
                return -1;
        }
        usleep(1000);
        x = 255;
        y = 1;
        printf("x=%d y=%d\n", x, y);
        pthread_join(t1, NULL);


}
结果输出一行打印后,陷入了死循环,问题现象一致。 尝试对应用层源代码反汇编一下:
objdump -S test > test.s

 00000000004006f0 <test_loop>:
{
  4006f0:       8b 15 5a 09 20 00       mov    0x20095a(%rip),%edx        #****赋值y****
        while (y == 0 ){
  4006f6:       85 d2                   test   %edx,%edx                  #****比较y和0的大小**** 
  4006f8:       74 1b                   je     400715 <test_loop+0x25>    #****跳转到400175偏移 test_loop****
{
  4006fa:       48 83 ec 08             sub    $0x8,%rsp
                printf("test_loop: x=%d y=%d\n", x, y);
  4006fe:       8b 35 50 09 20 00       mov    0x200950(%rip),%esi        
  400704:       bf b0 07 40 00          mov    $0x4007b0,%edi
  400709:       31 c0                   xor    %eax,%eax
  40070b:       e8 30 fe ff ff          callq  400540 <printf@plt> 
}
  400710:       48 83 c4 08             add    $0x8,%rsp
  400714:       c3                      retq
  400715:       eb fe                   jmp    400715 <test_loop+0x25>      # *****无限死循环************       
  400717:       66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
  40071e:       00 00 
反汇编的结果,可以看到的确在便宜“400715”的位置陷入了死循环。为什么会这样呢? 是编译器优化导致的吗? 对代码重新编译,不适用编译器优化:
[root]# gcc -g -O0 -o test mb_user.c -lpthread
[root]# ./test 
x=255 y=1
test_loop: x=255 y=1
现在输出显示正常。 这个问题是由于编译器优化导致的,y的值被更改之后,并没有被线程识别到,这个时候需要程序员在编码时就应该识别到会有这种现象,通过volatile 声明y向量
volatile int y = 0;
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,就会保证不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 对代码重新进行编写后,反复卸载加载驱动:
while true
do
insmod test.ko
rmmod test.ko
done
执行上百次后,出现:
Dec  1 01:31:37 localhost kernel: test_loop : x=255 y=1
Dec  1 01:31:37 localhost kernel: x=255 y=1
Dec  1 01:31:38 localhost kernel: test_loop : x=255 y=1
Dec  1 01:31:38 localhost kernel: x=255 y=1
Dec  1 01:31:39 localhost kernel: test_loop : x=255 y=1
Dec  1 01:31:39 localhost kernel: x=255 y=1
Dec  1 01:31:40 localhost kernel: test_loop : x=0 y=1
Dec  1 01:31:40 localhost kernel: x=255 y=1
Dec  1 01:31:41 localhost kernel: test_loop : x=255 y=1
Dec  1 01:31:41 localhost kernel: x=255 y=1
Dec  1 01:31:42 localhost kernel: test_loop : x=255 y=1
Dec  1 01:31:42 localhost kernel: x=255 y=1
其中出现了x=0 y=1的打印,为什么x在y之前赋值,反而出现了x=0的现象呢? 这个问题的主要原因是因为x和y变量弱依赖性,CPU在执行指令时乱序执行。 防止CPU在关键位置进行乱序执行,可以使用内存屏障保证。 在x和y赋值之间,加入内存屏障
x = 255;
smp_wmb();
y = 1;

 

 

 

   

标签:kernel,31,编程,localhost,内核,Linux,test,loop,255
From: https://www.cnblogs.com/lethe1203/p/18104840

相关文章

  • Linux服务器购买域名,申请免费的SSL证书
    .1.登录阿里云服务器,在搜索框中输入域名注册,点击域名。  然后就会跳转到这个页面  也有可能会跳转到下面这个页面。然后就可以在输入框中输入对应的域名,看看是否已被注册。  .2.选择好之后,加入购物车,付钱。最终显示的页面如下。  自己买的这个域名yilang......
  • C#多线程编程详细教学
     在C#中,多线程编程是一种非常重要的技术,它允许程序同时执行多个任务,从而提高了应用程序的响应性和整体性能。本文将详细介绍C#中的多线程编程,包括基本概念、线程创建、线程同步以及相关的代码示例。一、基本概念线程是操作系统进行运算调度的最小单位,它被包含在进程之中,是......
  • 【人工智能入门必看的最全Python编程实战(6)】
    ---------------------------------------------------------------------1.AIGC未来发展前景未完持续…1.1人工智能相关科研重要性拥有一篇人工智能科研论文及专利软著竞赛是保研考研留学深造以及找工作的关键门票!!!拥有一篇人工智能科研论文及专利软著竞赛是保研考研......
  • Delphi模式编程
    文章目录Delphi模式编程涉及以下几个关键方面:**设计模式的应用****Delphi特性的利用****实际开发中的实践**Delphi模式编程的实例Delphi模式编程是指在使用Delphi这一集成开发环境(IDE)和ObjectPascal语言进行软件开发时,采用设计模式(DesignPatterns)来解决常见编程问......
  • 2024年03月CCF-GESP编程能力等级认证C++编程八级真题解析
    本文收录于专栏《C++等级认证CCF-GESP真题解析》,专栏总目录:点这里。订阅后可阅读专栏内所有文章。一、单选题(每题2分,共30分)第1题为丰富食堂菜谱,炒菜部进行头脑风暴。肉类有鸡肉、牛肉、羊肉、猪肉4种,切法有肉排、肉块、肉末3种,配菜有圆白菜、油菜、豆腐3种,辣度有......
  • 2024年03月CCF-GESP编程能力等级认证C++编程七级真题解析
    本文收录于专栏《C++等级认证CCF-GESP真题解析》,专栏总目录:点这里。订阅后可阅读专栏内所有文章。一、单选题(每题2分,共30分)第1题下列关于排序的说法,正确的是()。A.冒泡排序是最快的排序算法之一。B.快速排序通常是不稳定的。C.最差情况,N个元素做归并排序......
  • Linux 限制root用户远程登录-ssh
    1.首先执行备份:#cp-p/etc/ssh/sshd_config/etc/ssh/sshd_config_bak2.确保系统存在除root之外的其他用户,防止设备配置完之后用户无法远程访问),若不存在其他用户,则使用如下命令添加用户并配置强密码:#useraddusername#passwdusername3.配置禁止root用户直接远程登录系统......
  • Linux永久修改主机名并生效
    1、前言RHEL6修改主机名的配置文件是 /etc/sysconfig/networkRHEL7修改主机名的配置文件是 /etc/hostname其次,主机名修改又分为临时修改和永久修改;还可划分为修改配置文件和用命令修改。 2、redhat7及以上版本修改方式hostnamectlset-hostnamemysql#立即刷新终......
  • 深入解析Java继承机制:面向对象编程的核心探究【Java面试题】
    作为一名对技术充满热情的学习者,我一直以来都深刻地体会到知识的广度和深度。在这个不断演变的数字时代,我远非专家,而是一位不断追求进步的旅行者。通过这篇博客,我想分享我在某个领域的学习经验,与大家共同探讨、共同成长。请大家以开放的心态阅读,相信你们也会在这段知识之......
  • 网络编程:百度api实现地理编码与逆地理编码
    1.使用geopy库实现百度地理位置编码功能:2.使用requests库实现百度地理位置编码功能:3.使用geocoder库实现百度地理位置编码功能:4.使用http.client库实现百度地理位置编码功能:5.使用socket库实现百度地理位置编码功能:6.使用学习的四个库实现百度地理位置逆编码功能:......