zoukankan      html  css  js  c++  java
  • 嵌入式软件工程师面试题目整理(二)

    @

    目录

    嵌入式软件工程师面试题目整理(二)

    linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些?

    区别:
      1.内和空间和用户空间的划分
      Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~4G.Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为"内核空间".而将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为"用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

    2.存储内容不同
      内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。
    下面总结了7种方式,主要对以前不是很熟悉的方式做了编程实现,以便加深印象。

    3.其他
      内核空间和用户空间上不同太多了,说不完,比如用户态的链表和内核链表不一样;用户态用printf,内核态用printk;用户态每个应用程序空间是虚拟的,相对独立的,内核态中却不是独立的,所以编程要非常小心。等等。

    用户空间与内核通信方式

      见前Linux部分

    字符设备和块设备的区别,请分别列举一些实际的设备说出它们是属于哪一类设备

      字符设备:字符设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少实现open,close,read和write系统调用。字符终端、串口、鼠标、键盘、摄像头、声卡和显卡等就是典型的字符设备。

      块设备:和字符设备类似,块设备也是通过/dev目录下的文件系统节点来访问。块设备上能够容纳文件系统,如:u盘,SD卡,磁盘等。

      字符设备和块设备的区别仅仅在于内核内部管理数据的方式,也就是内核及驱动程序之间的软件接口,而这些不同对用户来讲是透明的。在内核中,和字符驱动程序相比,块驱动程序具有完全不同的接口。

    公众号:嵌入式与Linux那些事 CSDN: 嵌入式与Linux那些事 来源网络,转载声明

    linux中系统调用过程?如:应用程序中read()在linux中执行过程即从用户空间到内核空间?

    必须知道的知识:

      (1) 在Linux文件系统中,每个文件都用一个struct inode结构体来描述,这个结构体记录了这个文件的所有信息,例如文件类型,访问权限等。
      (2) 在linux操作系统中,每个驱动程序在应用层的/dev目录或者其他如/sys目录下都会有一个文件与之对应。
      (3) 在linux操作系统中, 每个驱动程序都有一个设备号
      (4) 在linux操作系统中,每打开一次文件,Linux操作系统会在VFS层分配一个struct file结构体来描述打开的文件。

      注意:常常我们认为,struct inode描述的是文件的静态信息,即这些信息很少会改变,而struct file描述的是动态信息,即对文件的操作的时候,struct file里面的信息经常会发生变化。典型的是struct file结构体里面的f_ops(记录当前文件的位移量),每次读写一个普通文件时f_ops的值都会发生改变。
    在这里插入图片描述
      通过上图我们可以知道,如果想访问底层设备,就必须打开对应的设备文件。也就是在这个打开的过程中,Linux内核将应用层和对应的驱动程序关联起来。

      (1) 当open函数打开设备文件时,可以根据设备文件对应的struct inode结构体描述的信息,可以知道接下来要操作的设备类型(字符设备还是块设备),还会分配一个struct file结构体。
      (2) 根据struct inode结构体里面记录的设备号,可以找到对应的驱动程序。这里以字符设备为例。在Linux操作系统中每个字符设备都有一个struct cdev结构体。此结构体描述了字符设备所有信息,其中最重要的一项就是字符设备的操作函数接口。
      (3) 找到struct cdev结构体后,linux内核就会将struct cdev结构体所在的内存空间首地址记录在struct inode结构体i_cdev成员中,将struct cdev结构体中的记录的函数操作接口地址记录在struct file结构体的f_ops成员中
      (4) 任务完成,VFS层会给应用返回一个文件描述符(fd)这个fd是和struct file结构体对应的。接下来上层应用程序就可以通过fd找到struct file,然后在有struct file找到操作字符设备的函数接口了。

    公众号:嵌入式与Linux那些事 CSDN: 嵌入式与Linux那些事 来源网络,转载声明

    总结
      1.应用层调用open函数,在VFS层中找到struct inode结构体->>判断是字符设备还是块设备,根据设备号,可以找到对应的驱动程序

      2.在驱动层中,每个字符设备都有一个struct cdev结构体,这个结构体通过struct inode结构体中的i_cdev把连接起VFS层和驱动层,struct cdev结构体描述了字符设备所有信息,其中最重要的一项就是字符设备的操作函数接口

      3.struct cdev结构体中的struct file结构体记录了操作字符设备的一些函数,比如open read write函数等。
    struct file结构体其实是在VFS层的,通过struct file结构体指针指向驱动层的struct file结构体将驱动层函数和VFS层链接起来

      4.任务完成,VFS层会给应用返回一个文件描述符(fd)这个fd是和struct file结构体对应的

    查看驱动模块中打印信息应该使用什么命令?如何查看内核中已有的字符设备的信息?如何查看正在使用的有哪些中断号?

      1) 查看驱动模块中打印信息的命令:dmesg
      2) 查看字符设备信息可以用lsmod 和modprobe,ismod可以查看模块的依赖关系,modprobe在加载模块时会加载其他依赖的 模块。
       3) 显示当前使用的中断号cat /proc/interrupt

    copy_to_user()和copy_from_user()主要用于实现什么功能?一般用于file_operations结构的哪些函数里面?

       由于内核空间和用户空间是不能互相访问的,如果需要访问就必须借助内核函数进行数据读写。copy_to_user():完成内核空间到用户空间的复制,copy_from_user():是完成用户空间到内核空间的复制。一般用于file_operations结构里的read,write,ioctl等内存数据交换作用的函数。当然,如果ioctl没有用到内存数据复制,那么就不会用到这两个函数。

    请简述主设备号和次设备号的用途。如果执行mknod chartest c 4 64,创建chartest设备。请分析chartest使用的是那一类设备驱动程序。

       1)主设备号:主设备号标识设备对应的特定的驱动程序。虽然现代的linux内核允许多个驱动程序共享主设备号,但我们看待的大多数设备仍然按照“一个主设备对应一个驱动程序”的原则组织。

       次设备号:次设备号由内核使用,用于确定由主设备号对应驱动程序中的各个设备。。依赖于驱动程序的编写方式,我们可以通过次设备号获得一个指向内核设备的直接指针,也可将此设备号当作设备本地数组的索引。

       2)chartest 表示设备节点,4表示主设备号,64表示次设备号。(感觉类似于串口终端或者字符设备终端)。

    设备驱动程序中如何注册一个字符设备?分别解释一下它的几个参数的含义。

       注册一个字符设备驱动有两种方法:

      1) void cdev_init(struct cdev *cdev, struct file_operations *fops)
       该注册函数可以将cdev结构嵌入到自己的设备特定的结构中。cdev是一个指向结构体cdev的指针,而fops是指向一个类似于f file_operations结构(可以是file_operations结构,但不限于该结构)的指针。

       2) int register_chrdev(unsigned int major, const char *namem , struct file operations *fopen);

      该注册函数是早期的注册函数,major是设备的主设备号,name是驱动程序的名称,而fops是默认的file_operations结构(这是只限于file_operations结构)。对于register_chrdev的调用将为给定的主设备号注册0-255作为次设备号,并为每个 设备建 立一个对应的默认cdev结构。

    字符型驱动设备怎么创建设备文件?

       手动创建:mknod /dev/led c 250 0 其中dev/led 为设备节点 ,c 代表字符设备, 250代表主设备号, 0代表次设备号。

      还有UDEV/MDEV自动创建设备文件的方式,UDEV/MDEV是运行在用户态的程序,可以动态管理设备文件,包括创建和删除设备文件,运行在用户态意味着系统要运行之后。在 /etc/init.d/rcS 脚本文件中会执行 mdev -s 自动创建设备节点。

    insmod 一个驱动模块,会执行模块中的哪个函数?rmmod呢?这两个函数在设计上要注意哪些?遇到过卸载驱动出现异常没?是什么问题引起的?

      答: insmod调用init函数,rmmod调用exit函数。这两个函数在设计时要注意什么?卸载模块时曾出现卸载失败的情形,原因是存在进程正在使用模块,检查代码后发现产生了死锁的问题。

      要注意在init函数中申请的资源在exit函数中要释放,包括存储,ioremap,定时器,工作队列等等。也就是一个模块注册进内核,退出内核时要清理所带来的影响,带走一切不留下一点痕迹。

       在LCD驱动的 file_opreations结构体中有个relase函数,就算没有用到也要定义

    设备驱动模型三个重要成员是?platform总线的匹配规则是?在具体应用上要不要先注册驱动再注册设备?有先后顺序没?

      设备驱动模型三个重要成员是 总线、设备、驱动;
      platfoem总线的匹配规则是:要匹配的设备和驱动都要注册,设备可以在设备树里注册,也可以通过代码注册设备,匹配成功会去调用驱动程序里的probe函数(probe函数在这个platform_driver结构体中注册)。

    内核函数mmap的实现原理,机制?

    mmap系统调用(功能)
    void* mmap ( void * addr , size_t len , int prot , int flags ,int fd , off_t offset )
    内存映射函数mmap, 负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。

    列举最少3种你所知道的嵌入式的体系结构,并请说明什么是ARM体系结构。
    arm,mips,x86

    三种架构的区别
    1. ARM

      ARM是高级精简指令集的简称(Advanced RISC Machine),它是一个32位的精简指令集架构,但也配备16位指令集,一般来讲比等价32位代码节省达35%,却能保留32位系统的所有优势。

      ARM处理器的主要特点是:
      体积小、低功耗、低成本、高性能——ARM被广泛应用在嵌入式系统中的最重要的原因。
      支持Thumb(16位)/ARM(32位)双指令集,能很好的兼容8位/16位器件;
      大量使用寄存器,指令执行速度更快;
      大多数数据操作都在寄存器中完成;
      寻址方式灵活简单,执行效率高;
      指令长度固定;
      Load_store结构:在RISC中,所有的计算都要求在寄存器中完成。而寄存器和内存的通信则由单独的指令来完成。而在CSIC中,CPU是可以直接对内存进行操作的。
      流水线处理方式。

    2. MIPS
      MIPS架构(英语:MIPS architecture,为Microprocessor without interlocked piped stages architecture的缩写,亦为Millions of Instructions Per Second的相关语),是一种采取精简指令集(RISC)的处理器架构,1981年出现,由MIPS科技公司开发并授权,广泛被使用在许多电子产品、网络设备、个人娱乐装置与商业装置上。最早的MIPS架构是32位,最新的版本已经变成64位。

      它的基本特点是:
      包含大量的寄存器、指令数和字符;
      可视的管道延时时隙;
      这些特性使MIPS架构能够提供最高的每平方毫米性能和当今SoC设计中最低的能耗。

    3. X86
      X86架构是芯片巨头Intel设计制造的一种微处理器体系结构的统称。如果这样说你不理解,那么当我说出8086,80286等这样的词汇时,相信你肯定马上就理解了,正是基于此,X86架构这个名称被广为人知。

      如今,我们所用的PC绝大部分都是X86架构。可见X86架构普及程度,这也和Intel的霸主地位密切相关。

      

    x86采用CISC(Complex Instruction Set Computer,复杂指令集计算机)架构。与采用RISC不同的是,在CISC处理器中,程序的各条指令是按顺序串行执行的,每条指令中的各个操作也是按顺序串行执行的。顺序执行的优点是控制简单,但计算机各部分的利用率不高,执行速度慢。
    总结:
    在这里插入图片描述

    申请内存的方式

      见之前Linux部分整理

    IIC原理,总线框架,设备编写方法

      见之前Linux部分整理

    Linux中的用户模式和内核模式是什么含意

      见之前Linux部分整理

    怎样申请大块内核内存?

      vmalloc

    用户进程间通信主要哪几种方式

      见之前操作系统部分整理

    内核配置编译及Makefile?

     最近在学习Linux内核的配置、编译及Makefile文件。今天总结一下学习成果,分享给大家_

    1.解压缩打补丁
      首先是解压缩你获取到的Linux内核。这里我用到的是linux.2.22.6版本的内核。在Linux下命令行通过tar xjf linux.2.22.6.tar.bz2解压内核。然后,如果你需要对这个内核打补丁的话,用patch命令:patch -px <../linux.2.22.6.patch。这里的px指的是忽略掉补丁文件中描述的第几个斜杠。也就是忽略前x个目录。

    --- linux-2.6.22.6/arch/arm/configs/s3c2410_defconfig
    +++ linux-2.6.22.6_jz2440/arch/arm/configs/s3c2410_defconfig
    

      如果你此刻就在内核的根目录下,即linux-2.6.22.6下,也就是说打补丁需要忽略掉一个斜杠的目录。那么打补丁的命令就是patch -p1 <../linux.2.22.6.patch。

    2.配置内核
      现在补丁已经打好了,接下来就是配置内核了。这里配置有3种方法:
      1>直接进行make menuconfig。这是最麻烦的一种方法,所有的配置都需要你来操作。

      2>在默认配置上自己修改,也就是修改defconfig文件。使用 find -name "defconfig"查找你的架构对应的默认配置文件。我是在arch/arm/configs找到自己板子的默认配置文件。执行defconfig文件: make XXX_defconfig。XXX是你具体使用的板子型号。执行这一操作后,结果保存在.config文件。然后再执行make menuconfig命令。这时的配置就是在默认配置上稍加修改就可以了。

      3>使用厂家的配置文件。如果你的硬件有厂家提供的config文件那是最轻松的。直接cp XXX .config。然后执行make menuconfig。

      这里详细给大家讲一下内核的配置。Linux的内核配置,就是为了生成.config文件。因为在编译时需要用.config文件生成其他相关配置文件。我们的配置项大多是例如CONFIG_XXXDRIVER,这里的XXXDRIVER指的是各种驱动。我们需要告诉内核,这些驱动是编译进内核,还是编译成模块。通过查找CONFIG_XXXDRIVER,我们可以发现,它出现在四个地方:

      1>C源代码
      2>子目录Makefile:drivers/XXX/Makefile
      3>include/config/auto.conf
      4>include/linux/autoconf.h

      这里首先说明:.config文件在进行内核编译时(make uImage)生成了include/config/auto.conf和include/linux/autoconf.h。通过查看C源代码我们发现CONFIG_XXXDRIVER是一个宏定义,等于一个常量。在include/linux/autoconf.h中宏定义CONFIG_XXXDRIVER为一个常量,可能是0或1。那么现在有一个问题,就是CONFIG_XXXDRIVER到底被编译进内核还是编译成一个模块呢?这在C语言中是无法进行区分的,这种区分体现在哪里呢?这种区分体现在子目录的Makefile文件中。在子目录的Makefile中,若有 obj -y += XXX.o则表示XXX.c被编译进内核;obj -m +=XXX.o则表示XXX被编译成模块,为XXX.ko。include/config/auto.conf文件则是对CONFIG_XXXDRIVER进行赋值,为y时表示编译进内核,为m时表示编译成独立模块。

    #这里是include/config/auto.conf的部分内容
    # Automatically generated make config: don't edit
    # Linux kernel version: 2.6.22.6
    # Sun Nov 27 18:34:38 2016
    #
    CONFIG_CPU_S3C244X=y
    CONFIG_CPU_COPY_V4WB=y
    CONFIG_CRYPTO_CBC=y
    CONFIG_CPU_S3C2410_DMA=y
    CONFIG_CRYPTO_ECB=m
    CONFIG_SMDK2440_CPU2440=y
    复制代码
    复制代码
    #这里是drivers/i2c/Makefile
    # Makefile for the i2c core.
    #
    
    obj-$(CONFIG_I2C_BOARDINFO)    += i2c-boardinfo.o
    obj-$(CONFIG_I2C)        += i2c-core.o
    obj-$(CONFIG_I2C_CHARDEV)    += i2c-dev.o
    obj-y                += busses/ chips/ algos/
    
    ifeq ($(CONFIG_I2C_DEBUG_CORE),y)
    EXTRA_CFLAGS += -DDEBUG
    endif
    

    3.编译内核
      通过上面的描述,我们可以知道,在每个driver下,都有一个Makefile文件。来定义这个驱动是编译进内核还是编译成模块。这里稍稍提一下。上面我们讲到了在Makefile中单个文件怎样编译进内核和编译成模块。但是如果有两个以上的文件该如何书写呢?这里举个例子:obj -y += a.o b.o就表示将a.c和b.c编译进内核。

    obj -m += ab.o
    
    ab -objs := a.o b.o
    

      就可以表示将a.c和b.c共同编译成为一个模块。过程就是 a.c生成a.o , b.c生成b.o。a.o 和b.o共同生成ab.ko。在以往的对uboot启动内核的代码分析中,我们提到过编译内核生成的uImage是由两部分组成的:头部+Linux内核。这个头部包含了很多初始化的参数信息,例如内核的加载地址和入口地址。在编译内核时,我们直接执行make uImage即可。那么在文件中是怎样定义uImage的呢?又是怎样生成uImage的呢?

      首先,我们通过查找Makefile,发现uImage在arch/arm/Makefile中,而这个架构目录下的Makefile被包含进顶层目录的Makefile中。这样我们在执行 make uImage时,顶层目录的Makefile就可以调用架构子目录下的Makefile,实现对内核的编译,生成uImage。

    顶层目录下Makefile中相关命令:
    include $(srctree)/arch/$(ARCH)/Makefile
    架构目录下Makefile相关命令:
    zImage Image xipImage bootpImage uImage: vmlinux
    

      这就是刚刚所说的顶层Makefile调用架构目录下Makefile,架构目录下Makefile生成uImage,而且依赖于vmlinux文件。下面我们就开始讲解如何生成vmlinux文件。在顶层Makefile中,我们找到了有关生成vmlinux的大部分命令。

    顶层目录Makefile:
    init-y        := init/
    init-y        := $(patsubst %/, %/built-in.o, $(init-y)) 
    
    core-y        := usr/
    core-y        += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
    core-y        := $(patsubst %/, %/built-in.o, $(core-y)) 
    
    libs-y        := lib/
    libs-y1        := $(patsubst %/, %/lib.a, $(libs-y))
    libs-y2        := $(patsubst %/, %/built-in.o, $(libs-y))
    libs-y        := $(libs-y1) $(libs-y2) 
    
    drivers-y    := drivers/ sound/
    drivers-y    := $(patsubst %/, %/built-in.o, $(drivers-y))   
    
    net-y        := net/
    net-y        := $(patsubst %/, %/built-in.o, $(net-y)) = net/built-in.o
    
    
    vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) FORCE
    
    vmlinux-init := $(head-y) $(init-y)
    vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
    vmlinux-all  := $(vmlinux-init) $(vmlinux-main)
    vmlinux-lds  := arch/$(ARCH)/kernel/vmlinux.lds
    export KBUILD_VMLINUX_OBJS := $(vmlinux-all)
    
    
    
    架构目录Makefile:
    zImage Image xipImage bootpImage uImage: vmlinux
    
    head-y        := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
    

      我已经把顶层目录和架构目录下生成vmlinux的命令摘选出来。首先,我们看要想生成vmlinux,需要vmlinux-lds文件、vmlinux-init文件、vmlinux-main文件。其中,vmlinux-lds是链接脚本文件,定义了代码段,数据段的存放位置。这里我们接着往下看,vmlinux-init需要head-y和init-y,通过查看两个Makefile,我们可以得到经过转换后的结果:

    head-y        := 
    arch/arm/kernel/head$(MMUEXT).o    arch/arm/kernel/init_task.o
    
    init-y        := $(patsubst %/, %/built-in.o, $(init-y)) = init/built-in.o
    
    core-y        := $(patsubst %/, %/built-in.o, $(core-y)) 
                             = usr/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o
    
    libs-y        := $(libs-y1) $(libs-y2) =lib/lib.a lib/built-in.o
    
    drivers-y    := $(patsubst %/, %/built-in.o, $(drivers-y)) = drivers/built-in.o  sound/built-in.o  
    
    net-y        := $(patsubst %/, %/built-in.o, $(net-y)) = net/built-in.o    
    

      现在已经分析了内核编译的全部过程。那怎样知道我们分析的到底对不对,通过实际执行make uImage我们就可以看到执行过程。这是执行make uImage过程中的部分相关命令:

    arm-linux-ld -EL  -p --no-undefined -X -o vmlinux 
    -T arch/arm/kernel/vmlinux.lds 
    arch/arm/kernel/head.o 
    arch/arm/kernel/init_task.o  init/built-in.o --start-group  usr/built-in.o  arch/arm/kernel/built-in.o  arch/arm/mm/built-in.o  
    

      可以看到,首先目标要生成vmlinux,然后是链接脚本为vmlinux.lds。开始生成第一个文件:head.o,第二个文件:init_task.o。这和我们分析的完全一致。接下来以此类推,和我们分析的相同,也就是说我们分析的是正确的。

    SECTIONS
    {
    
    
    
     . = (0xc0000000) + 0x00008000;
    
     .text.head : {
      _stext = .;
      _sinittext = .;
      *(.text.head)
     }
    
     .init : { /* Init code and data        */
       *(.init.text)
      _einittext = .;
      __proc_info_begin = .;
       *(.proc.info.init)
      __proc_info_end = .;
      __arch_info_begin = .;
       *(.arch.info.init)
      __arch_info_end = .;
      __tagtable_begin = .;
       *(.taglist.init)
      __tagtable_end = .;
      . = ALIGN(16);
      __setup_start = .;
       *(.init.setup)
      __setup_end = .;
      __early_begin = .;
       *(.early_param.init)
      __early_end = .;
      __initcall_start = .;
    

      这是链接脚本vmlinux.lds中的部分内容。首先定义了虚拟地址:(0xc0000000) + 0x00008000。 然后是首先执行头部文件,这与我们分析的完全一致。代码段,初始化代码段等等。
      这就是Linux内核的从配置到编译的全部分析了_

    谈谈对Volatile关键字的理解

      见之前C语言部分整理

    framebuffer机制

       Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,通过mmap将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节,这些都是由Framebuffer设备驱动来完成的。通过mmap调用把显卡的物理内存空间映射到用户空间。

    spinlock与信号量的区别

     见之前操作系统部分整理

    linux中的同步机制

    说出你所知道的各类linux系统的各类同步机制(重点),什么是死锁?如何避免死锁(每个技术面试官必问)

       原子操作,不会被任何事务给打断,通常用于资源计数,引用计数。TCP/IP协议栈的IP碎片计数。

       信号量。就像一个房间有好几把钥匙,拿到钥匙就能进去访问。设置为1的时候变为了mutex。绝大部分情况下作为互斥锁使用。

       读写信号量。可以允许多个读,一个写。一旦有人在写,就大家都不能读。但是如果没人在写,就可以允许很多一起读。

       自旋锁。自旋锁与互斥锁的区别在于不会导致睡眠,如果自旋锁被其他执行单元持有了,那么调用者就一直自旋在那循环地看持有者是否已经释放,而不睡眠。在持有时间短的情况下使用会比互斥锁高效。

       顺序锁(seqlock)。读者可以在写的时候读,写者也可以在读的时候写,但是写与写之间还是互斥的。利用了一个序号,写的时候要对序号加1,这样读的人可以知道读的期间有没有人写。

    什么是死锁及死锁的必要条件和解决方法
       死锁。就是几个进程申请资源,出现了无限循环等待的现象。

       四个必要条件
       资源是互斥的
       不可抢占
       占有且申请
       循环等待

    解决方案也从这四个条件来着手
       1. 资源的互斥是客观的,要改变的话不现实。
       2. 申请资源不可满足的时候释放已经申请到的资源。
       3. 只有在全部资源都可以得到的情况下才一次性分配。
       4. 资源有序分配。给所有资源编号,进程对资源的请求必须是严格递增的序列,只有在占有了小号资源的情况下才能申请了大号资源。(假设打印机是小号资源,CDROM是大号资源)

    自旋锁和信号量在互斥使用时需要注意哪些?在中断服务程序里面的互斥是使用自旋锁还是信号量?还是两者都能用?为什么?

       使用自旋锁的进程不能睡眠,使用执行时间短的任务,使用信号量的进程可以睡眠,适合于执行时间较长的任务。中断服务例程中的互斥使用的是自旋锁,原因是在中断处理例程中,硬中断是关闭的,这样会丢失可能到来的中断。

    驱动里面为什么要有并发、互斥的控制?如何实现?讲个例子?

       并发(concurrency)指的是多个执行单元同时、并行被执行,而并发的执行单元对共 享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(race conditions)。

       解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问就是指一个执行单元 在访问共享资源的时候,其他的执行单元都被禁止访问。
       访问共享资源的代码区域被称为临界区,临界区需要以某种互斥机 制加以保护,中断屏蔽,原子操作,自旋锁,和信号量都是linux设备驱动中可采用的互斥途径。

    linux中断实现机制、tasklet和workqueue的区别和底层实现的区别,为什么要区分中断上半部和中断下半部

       见之前Linux部分整理

    中断和轮询哪个效率高?怎样决定是采用中断方式还是采用轮询方式去实现驱动?

       中断是CPU处于被动状态下来接受设备的信号,而轮询是CPU主动去查询该设备是否有请求。凡事都是两面性,所以,看效率不能简单的说那个效率高。如果是请求设备是一个频繁请求cpu的设备,或者有大量数据请求的网络设备,那么轮询的效率是比中断高。如果是一般设备,并且该设备请求cpu的频率比较底,则用中断效率要高一些。主要是看请求频率。

    写一个中断服务需要注意哪些?如果中断产生之后要做比较多的事情你是怎么做的?

       第一: 中断处理例程应该尽量短,把能放在后半段(tasklet,等待队列等)的任务尽量放在后半段。

       写一个中断服务程序要注意快进快出,在中断服务程序里面尽量快速采集信息,包括硬件信息,然后退出中断,要做其它事情可以使用工作队列或者tasklet方式。也就是中断上半部和下半部。

       第二:中断服务程序中不能有阻塞操作。应为中断期间是完全占用CPU的(即不存在内核调度),中断被阻塞住,其他进程将无法操作;

       第三:中断服务程序注意返回值,要用操作系统定义的宏做为返回值,而不是自己定义的OK,FAIL之类的。

    IRQ和FIQ有什么区别,在CPU里面是是怎么做的?

       FIQ和IRQ是两种不同类型的中断,ARM为了支持这两种不同的中断,提供了对应的叫做FIQ和IRQ处理器模式(ARM有7种处理模式)。

       一般的中断控制器里我们可以配置与控制器相连的某个中断输入是FIQ还是IRQ,所以一个中断是可以指定为FIQ或者IRQ的,为了合理,要求系统更快响应,自身处理所耗时间也很短的中断设置为FIQ,否则就设置了IRQ。

       如果该中断设置为了IRQ,那么当该中断产生的时候,中断处理器通过IRQ请求线告诉ARM,ARM就知道有个IRQ中断来了,然后ARM切换到IRQ模式运行。类似的如果该中断设置为FIQ,那么当该中断产生的时候,中断处理器通过FIQ请求线告诉ARM,ARM就知道有个FIQ中断来了,然后切换到FIQ模式运行。

    简单的对比的话就是FIQ比IRQ快,为什么快呢?

       ARM的FIQ模式提供了更多的banked寄存器,r8到r14还有SPSR,而IRQ模式就没有那么多,R8,R9,R10,R11,R12对应的banked的寄存器就没有,这就意味着在ARM的IRQ模式下,中断处理程序自己要保存R8到R12这几个寄存器,然后退出中断处理时程序要恢复这几个寄存器,而FIQ模式由于这几个寄存器都有banked寄存器,模式切换时CPU自动保存这些值到banked寄存器,退出FIQ模式时自动恢复,所以这个过程FIQ比IRQ快.

       FIQ比IRQ有更高优先级,如果FIQ和IRQ同时产生,那么FIQ先处理。

       在symbian系统里,当CPU处于FIQ模式处理FIQ中断的过程中,预取指令异常,未定义指令异常,软件中断全被禁止,所有的中断被屏蔽。所以FIQ就会很快执行,不会被其他异常或者中断打断,所以它又比IRQ快了。而IRQ不一样,当ARM处理IRQ模式处理IRQ中断时,如果来了一个FIQ中断请求,那正在执行的IRQ中断处理程序会被抢断,ARM切换到FIQ模式去执行这个FIQ,所以FIQ比IRQ快多了。

       另外FIQ的入口地址是0x1c,IRQ的入口地址是0x18。

    Linux软中断和工作队列的作用是什么

      见之前Linux部分

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    【PHP框架CodeIgniter学习】使用辅助函数—建立自己的JSONHelper
    mysql将字符转换成数字
    ***微信浏览器禁止app下载链接怎么办
    十分钟帮你拿到500万天使轮!手把手教你写商业计划书【干货】
    ***PHP各种编码的汉字字符串截取
    Nginx与Redis解决高并发问题
    hrtimer的简单使用 + 原理和实现【转】
    2.6 内核中的计时器和列表【转】
    Linux输入子系统:多点触控协议 -- multi-touch-protocol.txt【转】
    kthread_create与kernel_thread的区别【栈】
  • 原文地址:https://www.cnblogs.com/dongxb/p/14361076.html
Copyright © 2011-2022 走看看