day02

来源:互联网 发布:泼墨软件 编辑:程序博客网 时间:2024/06/05 16:12
回顾:
1.linux系统包括用户空间和内核空间
  用户空间特点
  内核空间特点
2.linux内核编程框架
  module_init
          insmod
  module_exit
          rmmod   
3.linux内核程序的编译
  Makefile
4.linux内核程序的命令行传参
  全局变量
  数据类型:没有float/double
  传参声明:module_param(name,type,perm)
5.linux内核符号导出
  目的:多文件之间的访问
  EXPORT_SYMBOL/EXPORT_SYMBOL_GPL
6.linux内核打印函数printk
  能够指定八个打印级别(数字越小,输出级别越高)
  通过默认打印数据级别决定打印信息是否打印输出
  默认打印级别的设置方法:两种
  echo xxx > /proc/sys/kernel/printk
  debug/quiet/loglevel
 
*************************************************
7.linux内核GPIO操作库函数
  1.明确:
    "GPIO操作":ARM处理器引脚具有复用功能,使用前
               记得先配置为GPIO功能
               一旦配置为GPIO功能,即可输入或者输出操作
                          GPIO操作又分:输入操作和输出操作
    "输入操作":此GPIO引脚的电平由外设来决定
    "输出操作": 此GPIO引脚的电平由CPU来决定
    “库函数”:linux内核已经帮你实现,你只需调用即可
              linux内核提供的库函数的实现定义在内核源码中
               
  2.linux内核提供的GPIO操作的库函数如下:
      int gpio_request(unsigned gpio,  
                                          const char *label)
    函数功能:CPU的任何一个GPIO引脚硬件资源对于
    linux内核来说都是一种宝贵的资源,如果某个内核
    程序要向访问这个GPIO引脚硬件资源,首先必须向
    linux内核申请资源(类似malloc)
    参数说明:
    gpio:GPIO引脚硬件在linux内核中的软件编号,也就是
         对于任何一个GPIO引脚,linux内核都给分配一个唯一的
         软件编号(类似GPIO引脚的身份证号)
         GPIO硬件       GPIO软件编号
         GPIOC12         PAD_GPIO_C+12
         GPIOB11         PAD_GPIO_B+11
         .......         ......          
    label:给申请的硬件GPIO引脚指定的名称,随便取
    返回值:看内核大神的代码如何判断即可,照猫画虎
    涉及头文件:只需将大神的代码使用的头文件全盘拷贝过来即可
                注意""包含的头文件不做参考,找别的代码
     
      void gpio_free(unsigned gpio)
      功能:内核程序如果不再使用访问GPIO硬件资源
            记得要将硬件资源归还给linux内核,类似free
      参数:
      gpio:要释放的GPIO硬件资源对应的软件编号
      
      int gpio_direction_output(unsigned gpio,  
                                                              int value)
      功能:配置GPIO引脚为输出功能,并且输出一个value值(1高电平/0低电平)                                                
      参数:
      gpio:GPIO硬件对应的软件编号
      value:输出的值
      
      int  gpio_direction_input(unsigned gpio)
      功能:配置GPIO为输入功能
      
      int gpio_set_value(unsigned gpio, int value)
      功能:设置GPIO引脚的输出值为value(1:高/0:低)
            前提是必须首先将GPIO配置为输出功能
             
      int gpio_get_value(unsigned gpio)
      功能:获取GPIO引脚的电平状态,返回值就是引脚的
      电平状态(返回1:高电平;返回0:低电平)
      此引脚到底是输入还是输出没关系!
      
      案例:利用linux内核GPIO库函数实现加载驱动开灯
            卸载驱动关灯
      回顾:标准C的结构体使用
      //声明描述LED硬件信息的数据结构
      struct led_resource {
          int gpio;  //LED灯对应GPIO引脚的软件编号
          char *name;//LED灯的名称
          int state; //LED的状态
      };
      
      //定义初始化四个LED灯的硬件信息对象   
      struct led_resource led_info[] = {
              {PAD_GPIO_C+12, "LED1", 0},
              {PAD_GPIO_C+11, "LED2", 0},
              {PAD_GPIO_C+10, "LED3", 0},
              {PAD_GPIO_C+9, "LED4", 0}
      };  
      以上初始化的缺点是state字段无需初始化,但是按照
      传统的结构体初始化方式,state字段是必须要初始化的
      否则编译报错!
      如何解决以上问题呢?也就是只初始化gpio和name字段
      而不用初始化state呢?
      答:利用结构体的标记初始化方式即可
      //定义初始化四个LED灯的硬件信息对象   
      struct led_resource led_info[] = {
              {
                  .name = "LED1",
                  .gpio = PAD_GPIO_C+12
              },
              {
                .name = "LED2",
                  .gpio = PAD_GPIO_C+11
              },
              {
                  .name = "LED3",
                  .gpio = PAD_GPIO_C+10
              },
              {
                  .name = "LED4",
                  .gpio = PAD_GPIO_C+9
              }
      };   
      结构体的标记初始化方式可以不用按照顺序,并且不用
      全部进行对成员初始化!
      
      上位机实施步骤:
      mkdir /opt/drivers/day02/1.0 -p
      cd /opt/drivers/day02/1.0
      vim led_drv.c
      vim Makefile
      make
      cp led_drv.ko /opt/rootfs/home/drivers
      
      下位机测试:
      cd /home/drivers
      insmod led_drv.ko //开灯
      rmmod led_drv //关灯
 
8.linux内核系统调用实现原理(了解)
  面试题:谈谈对linux系统调用的理解
  以write系统调用函数为例,掌握系统调用的实现过程:
  例如:
  int main(void)
  {
      write(1, "hello,world\n", 12);
      printf("hello,world\n");
      return 0;
  }  
  1.当应用程序(进程)调用write系统调用函数, 首先会跑到
    C库的write函数的定义实现的地方
  2.C库的write函数将会做两件事
    1.首先保存write函数的系统调用号到R7寄存器
      “系统调用号”:linux内核给每一个系统调用函数分配唯一的软件编号(类似系统调用函数的身份证号)
                                定义内核源码的arch/arm/include/asm/unistd.h
      例如:#define __NR_write            (0+4)  
    2.然后调用swi指令触发一个软中断异常
      新版本的ARM核(ARMV6开始)触发软中断的指令为svc
      老版本的ARM核触发软中断的指令是swi,现在的新版编译器
            同样支持swi指令!
            
    3.一旦触发软中断异常,CPU核立马要处理软中断异常
      硬件上自动将做:
          备份CPSR到SPSR_SVC
          设置CPSR
              MODE=SVC_MODE:切换SVC管理模式
              T=0:切换到ARM状态
              IF=11:禁止FIQ/IRQ中断     
          保存返回地址LR_SVC=PC-4
          设置PC=0x08,至此CPU跑到0x08软中断处理的入口地址
          至此开启了软件处理软中断异常的流程
          软中断的处理入口地址里相关的代码有linux内核来实现
          也就是说当前进程由用户空间"陷入"内核空间
      4.linux内核软中断处理的入口地址相关的代码将做
        以下工作:
        1.从R7寄存器取出之前保存的write函数的系统调用号4
        2.以write系统调用号4为下标在内核的系统调用表(数组)中
          找到对应的一个内核函数sys_write,一旦找到对应的
          内核函数,继续调用此函数,调用完毕,最后原路返回到
          用户空间,至此write函数返回!
          “系统调用表”:本质就是一个数组,数组元素就是函数指针
                                      数组元素的下标就是系统调用号
                                      其定义在内核源码的arch/arm/kernel/calls.S
       5.切记:要边说边画图!
**************************************************
8.linux内核设备驱动
  8.1.何为设备驱动?
  答:设备驱动的两大核心内容
      1.必须有对硬件的操作访问
      2.必须有硬件操作接口,用户能够通过这些接口来访问硬件
   
  8.2.linux内核设备驱动的分类
  字符设备驱动:对字符设备的访问按照字节流形式访问
              例如:LED,按键,蜂鸣器,GPS(UART),GPRS(UART),BT(UART)
                          触摸屏(XY绝对坐标),LCD显示屏(像素点)
                          声卡,摄像头,各种硬件传感器(三轴,重力,光线,距离,温度等)
                          EEPROM存储器(I2C接口)
  块设备驱动:对块设备的访问按照数据块进行,比如一次操作512字节
        例如:硬盘,U盘,TF卡,SD卡,Nandflash,Norflash,EMMC
  网络设备驱动:对网络设备驱动一般按照网络协议进行
              例如:有线网卡和无线网卡
   
  8.3.明确:linux系统的理念
  “一切皆文件”:任何硬件外设都是以文件的形式存放
                           用户访问文件本质上就是在访问对应的硬件外设
  字符设备对应的文件称之为字符设备文件
  块设备对应的文件称之为块设备文件
  网络设备无设备文件,通过socket套接字进行访问
   
  8.4.字符设备文件特点属性
  明确:字符设备文件本身代表的就是字符设备硬件本身
  明确:字符设备文件存在于根文件系统必要目录的dev目录下   
        当然块设备文件也存于dev目录下
  举例子:查看下位机的UART设备的字符设备文件
  ls /dev/ttySAC* -lh    得到以下信息:
  crw-rw----     204,  64  /dev/ttySAC0
  crw-rw----     204,  65  /dev/ttySAC1
  crw-rw----     204,  66  /dev/ttySAC2
  crw-rw----     204,  67  /dev/ttySAC3    
  说明:
  c:表示此设备文件对应的设备为字符设备
  204:表示串口的主设备号    
  64/65/66/67:分别表示第一个,第二个,第三个,第四个串口的次设备号
  ttySAC0:表示第一个串口的设备文件名
  ttySAC1:表示第二个串口的设备文件名
  ttySAC2:表示第三个串口的设备文件名
  ttySAC3:表示第四个串口的设备文件名          
  注意:一个硬件外设个体有唯一的一个设备文件名
   
  8.5.字符设备文件创建方法有两种:
  1.手动创建,只需mknod命令
    语法:
            mknod /dev/字符设备文件名 c 主设备号 次设备号
      例如:
              mknod /dev/zhangsan c 250 0
  2.自动创建    
              后续课程慢慢来
   
  8.6.字符设备文件的访问
    明确:访问字符设备文件本质就是在访问字符设备硬件
    明确:字符设备文件的访问必须利用系统调用函数
    例如:
    打开UART0:注意:使用绝对路径!
        int fd = open("/dev/ttySAC0", O_RDWR)    
        if (fd < 0)
            return -1;    
     
    从UART0读取数据:
    char buf[1024] = {0};
    read(fd, buf, 1024);
     
    向UART0写入数据:
    write(fd, "hello,world\n", 12);
     
    关闭UART0:          
    close(fd);     
     
  8.7.主设备号,次设备号,设备号
  主设备号作用:应用程序根据字符设备文件的主设备号
                              在茫茫的内核驱动中找到对应的唯一的
                              设备驱动,一个设备驱动仅有唯一的主设备号
  次设备号作用:设备驱动根据次设备号能够找到应用程序
                要访问的硬件外设个体
  总结:一个驱动仅有一个主设备号,一个硬件个体仅有一个次设备号
        应用根据主设备号找驱动,驱动根据次设备号找硬件个体
            所以:主,次设备号对于linux内核是一个宝贵的资源,某个
            设备驱动必须要想linux内核申请主,次设备号
            问:如何申请呢?
  设备号:设备号包含主,次设备号
                  linux内核用dev_t(unsigned int)数据类型描述设备号信息
                  设备号的高12位用来描述主设备号
                  设备号的低20位用来描述次设备号
                  设备号和主,次设备号之间的转换操作宏:
                  设备号=MKDEV(已知的主设备号,已知的次设备号);
                  主设备号=MAJOR(已知的设备号);
                  次设备号=MINOR(已知的设备号);  
                  作业:认真研读掌握MINOR宏的源码实现过程!
  问:既然设备号对于linux内核是一种宝贵的资源,设备驱动
      如何向内核申请资源呢?
  答:利用以下函数即可申请
  int alloc_chrdev_region(dev_t *dev,  
                                          unsigned baseminor,
                                          unsigned count,
                                          const char *name
                                          );
  函数功能:向内核申请设备号
  参数:
  dev:保存申请的设备号    
          包括主设备号和起始的次设备号
  baseminor:希望申请的起始次设备号,一般给0
  count:申请的次设备号的个数    
               如果baseminor=0,count=2,那么申请的次设备号分别是0和1
  name:申请设备号指定的名称,随便取    
              将来通过执行cat /proc/devices查看    
   
  设备驱动一旦不再使用设备号,记得要将设备号资源归还给linux内核:
  void unregister_chrdev_region(dev_t dev,  
                                                          unsigned count);
  功能:释放申请的设备号资源
  dev:申请的设备号
  count:申请的次设备号的个数
   
  8.8.自行设计数据结构来描述字符设备驱动属性
      //描述字符设备驱动
      struct char_device {
              dev_t dev; //描述申请的设备号
              int count; //描述申请的次设备号的个数
              int (*open)(...); //给用户提供打开设备的接口
              int (*close)(...); //给用户提供关闭设备的接口
              int (*read)(...); //给用户提供读设备接口
              int (*write)(...); //给用户提供写设备接口
      };
      浮想联翩,应用程序和驱动接口调用关系:
      应用程序open->软中断->内核的sys_open->驱动的open接口
      应用程序close->软中断->内核的sys_close->驱动的close接口
      应用程序read->软中断->内核的sys_read->驱动的read接口
      应用程序write->软中断->内核的sys_write->驱动的write接口
            
            优化:
            //描述字符设备驱动给用户提供的操作接口
            struct file_operations {
                  int (*open)(...); //给用户提供打开设备的接口
              int (*close)(...); //给用户提供关闭设备的接口
              int (*read)(...); //给用户提供读设备接口
              int (*write)(...); //给用户提供写设备接口
            };
            
            //描述字符设备驱动
            struct char_device {
              dev_t dev; //描述申请的设备号
              int count; //描述申请的次设备号的个数
              struct file_operations *ops;//给用户提供的操作接口
      };
    
   8.9.Linux内核描述字符设备驱动的数据结构
       Linux内核描述给用户提供操作接口的数据结构
                   //描述字符设备驱动给用户提供的操作接口
            struct file_operations {
                  int (*open) (struct inode *, struct file *); //给用户提供打开设备的接口
              int (*release) (struct inode *, struct file *); //给用户提供关闭设备的接口
                    ...
            };
            字符设备驱动和应用程序调用关系:
            应用程序open->软中断->内核的sys_open->驱动的open接口
      应用程序close->软中断->内核的sys_close->驱动的release接口
            
            //描述字符设备驱动
            struct cdev {
              dev_t dev; //描述申请的设备号
              int count; //描述申请的次设备号的个数
              struct file_operations *ops;//给用户提供的操作接口
              ...
      };
       
      配套函数:
      cdev_init(strcut cdev *cdev,  
                  struct file_operations *fops)
      函数功能:初始化字符设备驱动对象,就是给字符设备
      驱动对象添加一个硬件操作接口
      cdev:要初始化的字符设备对象
      fops:给用户提供的硬件操作接口
       
      cdev_add(struct cdev *cdev, dev_t dev, int count)
      函数功能:向内核注册添加一个字符设备对象,一旦添加完毕
      内核就有一个真实的字符设备驱动
      cdev:要注册的字符设备对象
      dev:申请的设备号
      count:申请的次设备号的个数
       
      cdev_del(struct cdev *cdev)
      函数功能:从内核中卸载字符设备对象,一旦卸载完毕
      内核就不会有一个真实的字符设备驱动
       
    8.10.编写字符设备驱动步骤
    1.定义初始化硬件操作接口对象
      struct file_operations led_fops = {
          .open = led_open, //打开设备接口
          .release = led_close, //关闭设备接口
      };  
     
    2.定义初始化字符设备对象
      struct cdev led_cdev; //定义字符设备对象
      //led_cdev.ops = &led_fops
      cdev_init(&led_cdev, &led_fops);//给字符设备对象添加硬件操作接口
            
        3.最终向内核注册字符设备对象
          cdev_add(&led_dev, 申请的设备号,次设备号的个数);
          一旦注册成功,内核就有一个真实的字符设备驱动,并且
          给用户提供硬件操作接口(open/release)
           
        4.从内核卸载字符设备对象
          cdev_del(&led_cdev);
           
        5.最后编写led_open/led_close接口
          具体内容根据用户需求来定
        
        案例:编写LED字符设备驱动,实现打开设备开灯,关闭设备关灯
        注意:将四个LED灯作为一个硬件设备个体
        实施步骤:
        上位机执行:
            mkdir /opt/drivers/day02/2.0
            cd /opt/drivers/day02/2.0
            vim led_drv.c //驱动
            vim led_test.c //应用
            vim Makefile
            make
            cp led_drv.ko /opt/rootfs/home/drivers
            arm-cortex_a9-linux-gnueabi-gcc -o led_test led_test.c
            cp led_test /opt/rootfs/home/drivers
            
        下位机测试:
        cd /home/drivers
        insmod led_drv.ko
        cat /proc/devices //查看申请的主设备号
            Character devices:
          主设备号   设备名称
          1                     mem
          5                 /dev/tty
            ...
            250             tarena
      mknod /dev/myled c 申请的主设备号 0 //创建设备文件myled
      ./led_test //运行
 
9.编写一个字符设备驱动的完整步骤
  1.先写头文件
  2.搭建框架
    入口函数
    出口函数
    各种修饰
    GPL
    入口和出口函数里面先不要写代码
  3.该声明的声明,该定义的定义,该初始化的初始化
    先搞定硬件然后软件
  4.填充入口和出口函数
    先写注释
    后写代码
  5.最后根据用户需求编写各个硬件操作接口函数
    可以放到前面写
    可以放到后面写,但要前面做声明
           
                         
     
原创粉丝点击