Linux设备驱动之——I2C总线

 I2C子系统

2.1 LinuxI2C子系统架构

在内核中已经提供I2C子系统,所以在做I2C驱动之前,就必须要熟悉该子系统。


2.2 三大组成部分

1、I2C核心(i2c-core)

I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(algorithm)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。

 

2、I2C总线驱动(I2Cadapter/Algo driver)

I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力。

I2C总线驱动由i2c_adapter和i2c_algorithm来描述

 

3、I2C客户驱动程序(I2Cclient driver)

I2C客户驱动是对I2C从设备的软件实现,一个具体的I2C客户驱动包括两个部分:一部分是i2c_driver,用于将设备挂接于i2c总线;另一部分是设备本身的驱动。

I2C客户驱动程序由i2c_driver和i2c_client来描述

 

2.3 所有的I2C驱动代码位于drivers/i2c目录下

I2c-core.c    实现I2C核心的功能

I2c-dev.c     通用的从设备驱动

Chips       特定的I2C设备驱动

Busses      I2C适配器的驱动

Algos       实现了一些I2C总线适配器的algorithm

 

2.4 I2C驱动编写的两种方法

从上面的图我们可以看到两种编写驱动方法,一种是利用系统提供的i2c-dev.c来实现一个i2c适配器的设备文件,然后通过在应用层操作I2C适配器来控制I2C设备;另一种是为I2C从设备独立编写一个设备驱动,不需要i2c-dev.c文件。

 

2.5 重要的数据结构

每次分析子系统免不了分析它的数据结构,OK我们先来分析一下。

I2c_adapter结构体代表I2C总线控制器


  1. struct i2c_adapter {  
  2.    struct module *owner;  
  3.    unsigned int class;       /*classes to allow probing for */  
  4.     const struct i2c_algorithm*algo; /* 总线上数据传输的算法*/  
  5.    void *algo_data;              /* algorithm 数据 */  
  6.    
  7.    int timeout;            /* injiffies */  
  8.    int retries;             /* 重试次数 */  
  9.     struct device dev;      /* the adapter device */  
  10.    
  11.    int nr;  
  12.    char name[48];                 /* 适配器名字 */  
  13.    struct completion dev_released;   /* 用于同步 */  
  14. };  

I2c_algorithm对应一套通信方法

  1. struct i2c_algorithm {  
  2.     int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, intnum);  
  3.    int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,  
  4.                unsigned short flags, charread_write,  
  5.                u8 command, int size, unioni2c_smbus_data *data);  
  6.     u32 (*functionality) (structi2c_adapter *);  
  7. };  

Functionality 函数用于返回algorithm所支持的通信协议,比如I2C_FUNC_I2C,I2C_FUNC_10BIT_ADDR等。

Master_xfer   函数实现总线上数据传输,与具体的适配器有关

 

Master_xfer函数实现模板

  1. static int i2c_adapter_xxx_xfer(structi2c_adapter *adap, struct i2c_msg *msgs, int num)  
  2. {  
  3.    ......  
  4.    for (i = 0; i < num; i++) {  
  5.        i2c_adapter_xxx_start();         /*产生起始位*/  
  6.        if (msgs[i]->flags & I2C_M_RD) {    /*读取*/  
  7.            i2c_adapter_xxx_setaddr((msg->addr << 1) | 1);  /*发送从设备地址*/  
  8.            i2c_adapter_xxx_wait_ack();   /*获得从设备的ACK*/  
  9. i2c_adapter_xxx_readbytes(msgs[i]->buf,msgs[i]->len);  /*读取len长度的数据到buf中*/  
  10.        } else {  
  11.            i2c_adapter_xxx_setaddr(msg->addr << 1);  
  12.            i2c_adapter_xxx_wait_ack();  
  13.            i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len);  
  14.        }  
  15.     }  
  16.    i2c_adapter_xxx_stop(); /*产生停止位*/  
  17. }  

上面调用的函数用于完成适配器的底层硬件操作,与I2C适配器和CPU的具体硬件直接相关,需要由工程师根据芯片的数据手册来实现。在内核源码中,针对不同的I2C适配器都有master_xfer的实现,风格与模板不尽相同,但是可以用该模板作为参考来看源代码,受益匪浅。

 

I2c_driver代表I2C从设备驱动

  1. struct i2c_driver {  
  2.          unsignedint class;  
  3.    
  4.          int(*attach_adapter)(struct i2c_adapter *) __deprecated; /*依附i2c适配器函数指针*/  
  5.          int(*detach_adapter)(struct i2c_adapter *) __deprecated;/*脱离i2c适配器函数指针*/  
  6.    
  7.          int (*probe)(struct i2c_client*, const struct i2c_device_id *);  
  8.          int (*remove)(struct i2c_client*);  
  9.    
  10.          int(*suspend)(struct i2c_client *, pm_message_t mesg);  
  11.          int(*resume)(struct i2c_client *);  
  12.          void(*alert)(struct i2c_client *, unsigned int data);  
  13.          int(*command)(struct i2c_client *client, unsigned int cmd, void *arg);  
  14.    
  15.          struct device_driver driver;  
  16.          const struct i2c_device_id*id_table;  /* 该驱动所支持的设备ID表 */  
  17.    
  18.          /*Device detection callback for automatic device creation */  
  19.          int(*detect)(struct i2c_client *, struct i2c_board_info *);  
  20.          constunsigned short *address_list;  
  21.          structlist_head clients;  
  22. };  

在新内核中,attach_adapter和detach_adapter已经被probe和remove取代

Id_table用于i2c_driver和i2c_client的匹配

 

I2c_client代表I2C从设备

  1. struct i2c_client {  
  2. unsigned short flags;                 /*I2C_CLIENT_TEN:使用10位从地址,I2C_CLIENT_PEC:使用SMBus包错误检测*/  
  3.          unsignedshort addr;                 /* chipaddress - NOTE: 7bit    */  
  4.          charname[I2C_NAME_SIZE];  
  5.          struct i2c_adapter *adapter; /* 依附的i2c_adapter   */  
  6.          struct i2c_driver *driver;         /* 依附的i2c_driver*/  
  7.          structdevice dev;             /* the devicestructure             */  
  8.          intirq;                         /* irq issuedby device               */  
  9.          structlist_head detected;  
  10. };  


2.6 核心层提供的接口函数

1、  增加/删除I2C适配器

int i2c_add_adapter(struct i2c_adapter *adapter)

int i2c_del_adapter(struct i2c_adapter *adap)

  1.    
  2. static int i2c_register_adapter(struct i2c_adapter *adap)  
  3. {  
  4. res = device_register(&adap->dev);  
  5.    
  6. if (adap->nr <__i2c_first_dynamic_bus_num)  
  7.            i2c_scan_static_board_info(adap);  
  8.    
  9. bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);  
  10. }  

Device_register(&adap->dev)  向I2C总线注册一个adapter设备

i2c_scan_static_board_info(adap)   注册所有已知的i2c_client

 

2、  增加/删除I2C从设备驱动

  1. int i2c_add_driver(struct i2c_driver *driver)  
  2. void i2c_del_driver(struct i2c_driver *driver)  
  3.    
  4. inti2c_register_driver(struct module *owner, struct i2c_driver *driver)  
  5. {  
  6.    
  7. /* add the driver to the list of i2c drivers inthe driver core */  
  8. driver->driver.owner = owner;  
  9. driver->driver.bus = &i2c_bus_type;  
  10.    
  11. res = driver_register(&driver->driver);  
  12.    
  13. /* Walk the adapters that are already present*/  
  14. i2c_for_each_dev(driver, __process_new_driver);  
  15. }  

driver_register(&driver->driver)   向I2C总线注册一个i2c_driver

 

 

3、  i2c传输,发送和接收

  1. int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg*msgs, int num)  
  2. int i2c_master_send(const struct i2c_client *client, constchar *buf, int count)  
  3. int i2c_master_recv(const struct i2c_client *client, char*buf, int count)  
  4.    
  5. int i2c_transfer(structi2c_adapter *adap, struct i2c_msg *msgs, int num)  
  6. {  
  7. if (adap->algo->master_xfer) {  
  8.            for (ret = 0, try = 0; try <=adap->retries; try++) {  
  9.                     ret = adap->algo->master_xfer(adap, msgs,num);  
  10.            }  
  11. }  
  12. }  

最终会调用到适配器实现的master_xfer函数来完成数据传输工作

3  i2c-dev

3.1 概述

之前在介绍I2C子系统时,提到过使用i2c-dev.c文件在应用程序中实现我们的I2C从设备驱动。不过,它实现的是一个虚拟,临时的i2c_client,随着设备文件的打开而产生,并随着设备文件的关闭而撤销。I2c-dev.c针对每个I2C适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,所以i2c-dev.c的主题是”i2c_driver成员函数+字符设备驱动”。

 

3.2 i2c-dev.c源码分析

初始化模块

  1. static int __init i2c_dev_init(void)  
  2. {  
  3.          res= register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);  
  4.    
  5.          i2c_dev_class= class_create(THIS_MODULE, "i2c-dev");  
  6.    
  7.          /*Keep track of adapters which will be added or removed later */  
  8.          res= bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);  
  9.    
  10.          /*绑定已经存在的适配器 */  
  11.          i2c_for_each_dev(NULL,i2cdev_attach_adapter);  
  12. }  

I2c-dev初始化函数主要做了注册名为”i2c”的字符设备文件和”i2c-dev”的类

 

i2cdev_read和i2cdev_write

I2c-dev.c中实现的i2cdev_read和i2cdev_write函数不具有太强的通用性,只适合下面这种单开始信号情况:


而不适合多开始信号的情况:


所以我们经常会使用i2cdev_ioctl函数的I2C_RDWR,在分析i2cdev_ioctl函数之前,我们需要了解一个结构体:

  1. /* This is the structure as used in theI2C_RDWR ioctl call */  
  2. struct i2c_rdwr_ioctl_data {  
  3.          structi2c_msg __user *msgs;         /* pointersto i2c_msgs */  
  4.          __u32nmsgs;                    /* number ofi2c_msgs */  
  5. };  

Msgs     表示单个开始信号传递的数据;

Nmsgs     表示有多少个msgs,比如上图,单开始信号时,nmsgs等于1;多开始信号时,nmsgs等于2

 

  1. struct i2c_msg {  
  2.          __u16addr;     /* slave address                         */  
  3.          __u16flags;  /* 默认为写入 */  
  4. #define I2C_M_TEN                  0x0010     /*this is a ten bit chip address */  
  5. #define I2C_M_RD           0x0001     /* read data,from slave to master */  
  6. #define I2C_M_NOSTART                  0x4000     /* if I2C_FUNC_PROTOCOL_MANGLING */  
  7. #define I2C_M_REV_DIR_ADDR     0x2000     /*if I2C_FUNC_PROTOCOL_MANGLING */  
  8. #define I2C_M_IGNORE_NAK          0x1000     /*if I2C_FUNC_PROTOCOL_MANGLING */  
  9. #define I2C_M_NO_RD_ACK           0x0800     /* if I2C_FUNC_PROTOCOL_MANGLING */  
  10. #define I2C_M_RECV_LEN               0x0400     /* length will be first received byte */  
  11.          __u16len;                  /* msg length                              */  
  12.          __u8*buf;                 /* pointer to msgdata                       */  
  13. };  

 

使用i2cdev_ioctl函数的I2C_RDWR指令会调用到i2cdev_ioctl_rdrw函数:

  1. static noinline inti2cdev_ioctl_rdrw(struct i2c_client *client,  
  2.                    unsignedlong arg)  
  3. {  
  4.          structi2c_rdwr_ioctl_data rdwr_arg;  
  5.          structi2c_msg *rdwr_pa;  
  6.          u8__user **data_ptrs;  
  7.          inti, res;  
  8.    
  9.          if(copy_from_user(&rdwr_arg,  
  10.                                (struct i2c_rdwr_ioctl_data __user *)arg,  
  11.                                sizeof(rdwr_arg)))  
  12.                    return-EFAULT;  
  13.    
  14.          if(rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)  
  15.                    return-EINVAL;  
  16.    
  17.          rdwr_pa= kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), GFP_KERNEL);  
  18.    
  19.          if(copy_from_user(rdwr_pa, rdwr_arg.msgs,  
  20.                                rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {  
  21.                    kfree(rdwr_pa);  
  22.                    return-EFAULT;  
  23.          }  
  24.    
  25.          res= i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);  
  26.          while(i-- > 0) {  
  27.                    if(res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) {  
  28.                             if(copy_to_user(data_ptrs[i], rdwr_pa[i].buf,  
  29.                                                 rdwr_pa[i].len))  
  30.                                      res= -EFAULT;  
  31.                    }  
  32.                    kfree(rdwr_pa[i].buf);  
  33.          }  
  34. }  

咋一看,还挺复杂,其实主要做了一件事情:把用户空间传递过来的i2c_rdwr_ioctl_data数据进行错误检查,然后调用i2c_transfer函数与适配器进行通信,如果是接收数据,代码会将访问到的数据传回i2c_rdwr_ioctl_data的buf中。I2c_transfer最终会调用到I2C适配器具体实现的master_xfer函数来与硬件进行通信。

 

3.3 eeprom实例

预备知识

使用的mini2440开发板,eeprom的地址为0x50,实验完成一个数据的读写,先看下读写时序

AT24C08任意地址字节写的时序:


AT24C08任意地址字节写的时序:


下面的代码可以按照上面的两个图来阅读:

  1. #include <stdio.h>  
  2. #include <linux/types.h>  
  3. #include <fcntl.h>  
  4. #include <unistd.h>  
  5. #include <stdlib.h>  
  6. #include <sys/types.h>  
  7. #include <sys/ioctl.h>  
  8. #include <errno.h>  
  9. #include <assert.h>  
  10. #include <string.h>  
  11. #include <linux/i2c.h>  
  12. #include <linux/i2c-dev.h>  
  13.    
  14. int main()  
  15. {  
  16.          intfd, ret;  
  17.          unsignedchar rdwr_addr = 0x42;   /* e2prom 读写地址 */  
  18.          unsignedchar device_addr = 0x50; /* e2prom 设备地址 */  
  19.          unsignedchar data = 0x12;  /* 向e2prom写的数据 */  
  20.          structi2c_rdwr_ioctl_data e2prom_data;  
  21.    
  22.          fd= open("/dev/i2c/0", O_RDWR);  
  23.          if(fd < 0) {  
  24.                    perror("openerror");  
  25.                    exit(1);  
  26.          }  
  27.    
  28.          e2prom_data.msgs= (struct i2c_msg *)malloc(e2prom_data.nmsgs * \  
  29.                                                sizeof(structi2c_msg));  
  30.          if(e2prom_data.msgs == NULL) {  
  31.                    perror("mallocerror");  
  32.                    exit(1);  
  33.          }  
  34.    
  35.          ioctl(fd,I2C_TIMEOUT, 1); /* 设置超时 */  
  36.          ioctl(fd,I2C_RETRIES, 2); /* 设置重试次数 */  
  37.    
  38.           
  39.          /*向e2prom的rdwr_addr地址写入数据data*/  
  40.          e2prom_data.nmsgs= 1;  
  41.          e2prom_data.msgs[0].len= 2;  
  42.          e2prom_data.msgs[0].addr= device_addr;  
  43.          e2prom_data.msgs[0].flags= 0;     /* write */  
  44.    
  45.           
  46.          e2prom_data.msgs[0].buf= (unsigned char *)malloc(2);  
  47.          e2prom_data.msgs[0].buf[0]= rdwr_addr;    /* write address */  
  48.          e2prom_data.msgs[0].buf[1]= data;      /* write data */  
  49.    
  50.          ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);  
  51.          if(ret < 0) {  
  52.                    perror("writedata error");  
  53.                    exit(1);  
  54.          }  
  55.          printf("writedata: %d to address: %#x\n", data, rdwr_addr);  
  56.          data= 0;  /* be zero*/  
  57.    
  58.    
  59.          /*从e2prom的rdwr_addr地址读取数据存入buf*/  
  60.          e2prom_data.nmsgs= 2;  
  61.          e2prom_data.msgs[0].len= 1;  
  62.          e2prom_data.msgs[0].addr= device_addr;  
  63. //      e2prom_data.msgs[0].flags= 0;     /* write */  
  64.          e2prom_data.msgs[0].buf= &rdwr_addr;  
  65.    
  66.          e2prom_data.msgs[1].len= 1;  
  67.          e2prom_data.msgs[1].addr= device_addr;  
  68.          e2prom_data.msgs[1].flags= 1;     /* read */  
  69.          e2prom_data.msgs[1].buf= &data;  
  70.    
  71.          ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);  
  72.          if(ret < 0) {  
  73.                    perror("readerror");  
  74.                    exit(1);  
  75.          }  
  76.          printf("read  data: %d from address: %#x\n", data,rdwr_addr);  
  77.           
  78.          free(e2prom_data.msgs);  
  79.          close(fd);  
  80.    
  81.          return0;  
  82. }  

4 总线驱动

4.1 概述

I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力,比如起始,停止,应答信号和master_xfer的实现函数。

I2C总线驱动由i2c_adapter和i2c_algorithm来描述

 

4.2 S3c2440I2C控制器的硬件描述

S3c2440处理器内部集成了一个I2C控制器,通过四个寄存器来进行控制:

IICCON     I2C控制寄存器

IICSTAT     I2C状态寄存器

IICDS       I2C收发数据移位寄存器

IICADD     I2C地址寄存器

通过IICCON,IICDS,IICADD寄存器操作,可在I2C总线上产生开始位、停止位、数据和地址,而传输的状态则通过IICSTAT寄存器来获取。

 

4.3 i2c-s3c2410总线驱动分析(platform_driver)

I2C总线驱动代码在drivers/i2c/busses/i2c-s3c2410.c,这个代码同样支持s3c2410,s3c6410,s5pc110等Samsung 系列的芯片。

 

初始化模块和卸载模块

  1. static int __init i2c_adap_s3c_init(void)  
  2. {  
  3.          returnplatform_driver_register(&s3c24xx_i2c_driver);  
  4. }  
  5.    
  6. static void __exit i2c_adap_s3c_exit(void)  
  7. {  
  8.          platform_driver_unregister(&s3c24xx_i2c_driver);  
  9. }  

总线驱动是基于platform来实现的,很符合设备驱动模型的思想。

  1. static struct platform_drivers3c24xx_i2c_driver = {  
  2.          .probe                = s3c24xx_i2c_probe,  
  3.          .remove            = s3c24xx_i2c_remove,  
  4.          .id_table  = s3c24xx_driver_ids,  
  5.          .driver                = {  
  6.                    .owner     = THIS_MODULE,  
  7.                    .name       = "s3c-i2c",  
  8.                    .pm  = S3C24XX_DEV_PM_OPS,  
  9.                    .of_match_table= s3c24xx_i2c_match,  
  10.          },  
  11. };  

s3c24xx_i2c_probe函数

当调用platform_driver_register函数注册platform_driver结构体时,如果platformdevice 和 platform driver匹配成功后,会调用probe函数,来初始化适配器硬件。

  1. static int s3c24xx_i2c_probe(structplatform_device *pdev)  
  2. {  
  3.          ……  
  4.          /*初始化适配器信息 */  
  5.          strlcpy(i2c->adap.name,"s3c2410-i2c"sizeof(i2c->adap.name));  
  6.          i2c->adap.owner   = THIS_MODULE;  
  7.          i2c->adap.algo    = &s3c24xx_i2c_algorithm;  
  8.          i2c->adap.retries= 2;  
  9.          i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;  
  10.          i2c->tx_setup     = 50;  
  11.    
  12.          /*初始化自旋锁和等待队列头 */  
  13.          spin_lock_init(&i2c->lock);  
  14.          init_waitqueue_head(&i2c->wait);  
  15.    
  16.          /*映射寄存器 */  
  17.          res= platform_get_resource(pdev, IORESOURCE_MEM, 0);  
  18.          i2c->ioarea= request_mem_region(res->start, resource_size(res),  
  19.                                                 pdev->name);  
  20.          i2c->regs= ioremap(res->start, resource_size(res));  
  21.    
  22.          /*设置I2C核心需要的信息 */  
  23.          i2c->adap.algo_data= i2c;  
  24.          i2c->adap.dev.parent= &pdev->dev;  
  25.    
  26.          /*初始化I2C控制器 */  
  27.          ret= s3c24xx_i2c_init(i2c);  
  28.    
  29.          /*申请中断 */  
  30.          i2c->irq= ret = platform_get_irq(pdev, 0);  
  31.          ret= request_irq(i2c->irq, s3c24xx_i2c_irq, 0,  
  32.                               dev_name(&pdev->dev), i2c);  
  33.    
  34.    /* 注册I2C适配器 */  
  35.          ret= i2c_add_numbered_adapter(&i2c->adap);  
  36.          ……  
  37. }  

Probe主要工作是时能硬件并申请I2C适配器使用的IO地址,中断号等,然后向I2C核心添加这个适配器。I2c_adapter注册过程i2c_add_numbered_adapter->i2c_register_adapter

 

I2C总线通信方法

  1. static const struct i2c_algorithms3c24xx_i2c_algorithm = {  
  2.          .master_xfer             = s3c24xx_i2c_xfer,  
  3.          .functionality             = s3c24xx_i2c_func,  
  4. };  

s3c24xx_i2c_xfer函数是总线通信方式的具体实现,依赖于s3c24xx_i2c_doxfer和s3c24xx_i2c_message_start两个函数;

  1. static int s3c24xx_i2c_doxfer(structs3c24xx_i2c *i2c,  
  2.                                   struct i2c_msg *msgs, int num)  
  3. {  
  4.          ret =s3c24xx_i2c_set_master(i2c);  
  5.    
  6.          i2c->msg     = msgs;  
  7.          i2c->msg_num= num;  
  8.          i2c->msg_ptr= 0;  
  9.          i2c->msg_idx= 0;  
  10.          i2c->state   = STATE_START;  
  11.    
  12.          s3c24xx_i2c_message_start(i2c,msgs);  
  13. }  

首先设置s3c I2C设备器为主设备,然后调用s3c24xx_i2c_message_start函数启动I2C消息传输。

s3c24xx_i2c_func函数返回适配器所支持的通信功能。

 

4.4 适配器的设备资源(platform_device)

S3c2440的I2C总线驱动是基于platform来实现,前面我们分析了platformdriver部分,再来看下platform device部分。

在arch/arm/plat-samsung/dev-i2c0.c文件中定义了platform_device结构体以及I2C控制器的资源信息:

  1. static struct resource s3c_i2c_resource[] ={  
  2.          [0]= {  
  3.                    .start= S3C_PA_IIC,  
  4.                    .end   = S3C_PA_IIC + SZ_4K - 1,  
  5.                    .flags= IORESOURCE_MEM,  
  6.          },  
  7.          [1]= {  
  8.                    .start= IRQ_IIC,  
  9.                   .end  = IRQ_IIC,  
  10.                    .flags= IORESOURCE_IRQ,  
  11.          },  
  12. };  
  13.    
  14. struct platform_device s3c_device_i2c0 = {  
  15.          .name                 = "s3c2410-i2c",   /* 设备名 */  
  16. #ifdef CONFIG_S3C_DEV_I2C1  
  17.          .id               = 0,  
  18. #else  
  19.          .id               = -1,  
  20. #endif  
  21.          .num_resources         =ARRAY_SIZE(s3c_i2c_resource),  
  22.          .resource   =s3c_i2c_resource,  
  23. };  
  24.    
  25. struct s3c2410_platform_i2cdefault_i2c_data __initdata = {  
  26.          .flags                  = 0,  
  27.          .slave_addr      = 0x10,  /* I2C适配器的地址 */  
  28.          .frequency        = 100*1000,  /* 总线频率 */  
  29.          .sda_delay        = 100,   /* SDA边沿延迟时间ns */  
  30. };  
  31.    
  32. void __init s3c_i2c0_set_platdata(structs3c2410_platform_i2c *pd)  
  33. {  
  34.          structs3c2410_platform_i2c *npd;  
  35.    
  36.          if(!pd)  
  37.                    pd= &default_i2c_data;  
  38.    
  39.          npd= s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c),  
  40.                                    &s3c_device_i2c0);  
  41.    
  42.          if(!npd->cfg_gpio)  
  43.                    npd->cfg_gpio= s3c_i2c0_cfg_gpio;  
  44. }  

在板文件中把platform_device注册进内核:

  1. static struct platform_device*mini2440_devices[] __initdata = {  
  2.          ……  
  3.          &s3c_device_i2c0,  
  4. ……  
  5. };  

调用s3c_i2c0_set_platdata 函数把适配器具体的数据赋值给dev.platform_data:

  1. static void __init mini2440_init(void)  
  2. {  
  3.          ……  
  4. s3c_i2c0_set_platdata(NULL);  
  5. }  

I2C总线驱动就分析到这里。

5 客户驱动

5.1 概述

I2C客户驱动是对I2C从设备的实现,一个具体的I2C客户驱动包括两个部分:一部分是i2c_driver,用于将设备挂接于i2c总线;另一部分是设备本身的驱动。

I2C客户驱动程序主要由i2c_driver和i2c_client来描述。

 

5.2 实例源码分析

好了,我们来深入了解客户驱动代码的实现,drivers/misc/eeprom/at24.c文件支持大多数I2C接口的eeprom

I2c_driver实现

  1. static struct i2c_driver at24_driver = {  
  2.          .driver= {  
  3.                    .name= "at24",  
  4.                    .owner= THIS_MODULE,  
  5.          },  
  6.          .probe= at24_probe,  /* 当i2c_client和i2c_driver匹配时调用 */  
  7.          .remove= __devexit_p(at24_remove), /* 注销时调用 */  
  8.          .id_table= at24_ids,   /* i2c_driver支持的i2c_client类型 */  
  9. };  

初始化和卸载

  1. static int __init at24_init(void)  
  2. {  
  3.          returni2c_add_driver(&at24_driver);  
  4. }  
  5.    
  6. static void __exit at24_exit(void)  
  7. {  
  8.          i2c_del_driver(&at24_driver);  
  9. }  

At24_Probe函数

  1. static int at24_probe(struct i2c_client*client, const struct i2c_device_id *id)  
  2. {  
  3.          ……  
  4.           
  5.          /* 
  6.           * Export the EEPROM bytes through sysfs, sincethat's convenient. 
  7.           * By default, only root should see the data(maybe passwords etc) 
  8.           */  
  9.          sysfs_bin_attr_init(&at24->bin);  
  10.          at24->bin.attr.name= "eeprom";  
  11.          at24->bin.attr.mode= chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;  
  12.          at24->bin.read= at24_bin_read;  
  13.          at24->bin.size= chip.byte_len;  
  14.    
  15.          at24->macc.read= at24_macc_read;  
  16.         writable = !(chip.flags &AT24_FLAG_READONLY);  
  17.          if(writable) {  
  18.                    if(!use_smbus || i2c_check_functionality(client->adapter,  
  19.                                      I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)){  
  20.    
  21.                             unsignedwrite_max = chip.page_size;  
  22.    
  23.                             at24->macc.write= at24_macc_write;  
  24.    
  25.                             at24->bin.write= at24_bin_write;  
  26.                             at24->bin.attr.mode|= S_IWUSR;  
  27.                             ……  
  28.          }  
  29. ……  
  30.           
  31. err = sysfs_create_bin_file(&client->dev.kobj,&at24->bin);  
  32.          if(err)  
  33.                    gotoerr_clients;  
  34.    
  35.          i2c_set_clientdata(client,at24);  
  36.          ……  
  37. }  

Probe函数主要的工作是在sys目录下创建bin节点文件,用户可以同此节点文件来操作eeprom,并提供操作方法(read,write)

 

5.3  I2c_client实现

At24c不依赖于具体的CPU和I2C控制器硬件特性,因此如果电路板包含该外设,只需要添加对应的i2c_board_info,下面是at24c08 i2c_client在板文件中的实现:

  1. static struct at24_platform_data at24c08 ={  
  2.          .byte_len = SZ_8K / 8,   /* eeprom的存储大小,单位Byte */  
  3.          .page_size        = 16,      /* 页大小 Byte */  
  4. };  
  5.    
  6. static struct i2c_board_infomini2440_i2c_devs[] __initdata = {  
  7.          {  
  8.                    I2C_BOARD_INFO("24c08",0x50),          /* 24c08设备名,0x50设备地址 */  
  9.                    .platform_data= &at24c08,              /* 赋值给client->dev->platform_data */  
  10.          },  
  11. };  
  12. static void __init mini2440_init(void)  
  13. {  
  14.          ……  
  15.          i2c_register_board_info(0,mini2440_i2c_devs,    /* busnum = 0,busnum是适配器编号,用来识别从设备使用的哪个适配器 */  
  16.                                      ARRAY_SIZE(mini2440_i2c_devs));  
  17.          ……  
  18. }  

I2c_register_board_info函数会把I2C从设备硬件特性信息注册到全局链表__i2c_board_list,在调用i2c_add_adapter函数时,会遍历__i2c_board_list获得从设备信息来构造i2c_client。

 

I2c_client的构建

我们调用I2c_register_board_info函数会把I2C从设备硬件特性信息注册到全局链表__i2c_board_list,但是还没有构建出一个i2c_client结构体,也没有注册进I2C总线。我们来分析一下构造的过程,调用i2c_add_adapter函数时,会遍历__i2c_board_list获得从设备信息来构造i2c_client:i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()->device_register()。

 

5.4  I2c_driver和i2c_client的match

在调用i2c_add_driver注册i2c_driver和构建i2c_client时,都会调用i2c bus中注册的i2c_device_match()->i2c_match_id()函数通过i2c_driver->id_table->name和client->name来匹配

  1. static const struct i2c_device_id*i2c_match_id(const struct i2c_device_id *id,  
  2.                                                         conststruct i2c_client *client)  
  3. {  
  4.          while(id->name[0]) {  
  5.                    if(strcmp(client->name, id->name) == 0)  
  6.                             returnid;  
  7.                    id++;  
  8.          }  
  9.          returnNULL;  
  10. }  

5.5 测试

已在mini2440上实验成功,在/sys/bus/i2c/devices/0-0050/目录下(50代表从设备地址)会产生一个eeprom文件,这个文件相当于是硬件设备eeprom的映射,我们可以像普通文件一样对eeprom文件进行操作,实质上就是就硬件eeprom的操作。重启开发板,你会发现对eeprom文件修改过的内容不会改变,这就证明实验成功了,要知道sys文件系统是无法对数据保存的。

6 总结

下图根据之前的分析丰富的架构图


Tips:I2C适配器驱动不一定是基于platform实现,这里是以s3c-i2c为例。

 

I2c_driver、i2c_client与i2c_adapter

I2c_driver与i2c_client是一对多的关系,一个i2c_driver上可以支持多个同等类型的i2c_client。调用i2c_add_driver函数将I2c_driver注册到I2C总线上,调用i2c_register_board_info函数将i2c_client注册到全局链表__i2c_board_list。当调用i2c_add_adapter注册适配器时,遍历__i2c_board_list链表,i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()会构建i2c_client结构。当调用i2c_add_driver时,会先注册i2c_driver到I2C总线上,然后调用I2C BUS注册的match函数进行匹配,如果匹配成功,则先调用I2C BUS中注册的probe函数,在调用i2c_driver中实现的probe函数,完成相应的工作。


转载:http://blog.csdn.net/column/details/i2c-subsystem.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对考试很有帮助的.......... 《计算机操作系统》期末复习指导 第一章 计算机操作系统概述 1、操作系统的概念 操作系统(Operating System,OS),是一种软件,属于系统软件; 1、科普的观点 操作系统是计算机系统的管理和控制中心,它依照设计者制定的各种调度策略组织和管理计算机系统资源,使之能高效地运行。 2、功能的观点 操作系统是一个计算机资源管理系统,它负责计算机系统的全部资源的分配、控制、调度和回收。 3、用户的观点 操作系统是计算机与用户之间的接口,用户通过这种接口使用计算机。 4、软件的观点 操作系统是程序和数据结构的集合。 5、管理的观点 操作系统是计算机硬件和软件资源的合理而协调的管理者。 6、 操作系统是一个大型的程序系统,它负责计算机的全部软、硬件资源的分配、调度工作,控制并协调并发活动,实现信息的存取和保护。它提供用户接口,使用户获得良好的工作环境。操作系统使整个计算机系统实现了高效率和高度自动化。 2、操作系统的生成和五大类型 生成:产生最适合自己工作环境的OS内核(kernel)。既方便用户,又使系统开销尽量小;生成的配置过程如UNIX中newconfig命令;DOS中config.sys文件;维护由系统管理员负责。 操作系统的五大类型是批处理操作系统、分时操作系统、实时操作系统、网络操作系统、分布式操作系统。 多道程序设计:即在系统内(内存)同时存放并运行几道相互独立的程序。 多道程序设计的基础:是将运行过程进一步细化成几个小的步骤,从而实现宏观上的并行。但从微观上看,内存中的多道程序轮流地或分时地占用处理机,交替执行。 多道程序系统 ≠ 多重处理系统 ≠ 多用户 ≠ 多终端 多道是指内存中驻留多个程序或一个程序的多个程序段,因此,多用户系统一定是采用多道技术。而多道系统不一定是多用户系统。多重处理系统一般指多CPU系统。当然,一个CPU的系统采用分时技术可以为多用户服务。多用户的关键技术是在用户之间要有保密保安措施。终端指用户使用的硬件设备,即使一个终端也可为多用户使用,例如,银行的自动取款机(ATM)。 •分时与实时 分时技术:把CPU的时间分成很短的时间片(例如,几十至几百毫秒)工作。随着时间片的时间减少,对换时间所占的比例随之增大。随着用户数目的不断增加,这种矛盾会越来越突出。 实时是指计算机对于外来信息能够以足够快的速度进行处理,并在被控对象允许的时间范围内做出快速反应。交互作用能力较差。 3、操作系统的五大功能 •作业管理:包括任务管理、界面管理、人机交互、图形界面、语音控制和虚拟现实等; •文件管理:又称为信息管理; •存储管理:实质是对存储“空间”的管理,主要指对内存的管理; •设备管理:实质是对硬件设备的管理,其中包括对输入输出设备的分配、启动、完成和回收; •进程管理:又称处理机管理,实质上是对处理机执行“时间”的管理,即如何将CPU真正合理地分配给每个任务。 4、表征操作系统的属性 主要有:响应比,并发性,信息的共享、保密与保护,可扩充性、可移植性、可读性、可“生成”性,安全可靠性,可测试性等。 第二章 用户与操作系统的接口 1、基本概念 作业(Job)是让计算机完成一件事或任务,可大可小,可多可少。 作业步(Job steps) :作业顺序执行的工作单元。 作业流(Job Stream) :作业步的控制流程。 作业类别:终端交互作业、批处理作业。 2、用户界面 三代用户界面: •第一代用户界面:操作命令和系统调用在一维空间(命令行界面); •第二代用户界面:图形界面在二维空间(图形界面); •第三代用户界面:虚拟现实在三维空间(虚拟现实的界面元素)。 3、传统的人机接口 •操作命令 联机(键盘操作命令)、脱机(作业控制语言) 用户组合自编(Shell语言):DOS Shell;UNIX ;BShell、CShell等 •系统调用(System Call) 4、作业输入输出方式 •输入输出方式:脱机、直接耦合(交互联机) •SPOOLing:联机外围同时操作,假脱机(排队转储,设备虚拟技术) 5、作业调度 •作业调度的功能: (1)采用JCB(作业控制块)表格,记录各作业状况; (2)按选定的算法,从后备作业队列中选出一部分(多道)或一个作业投入运行; (3)为被选中的作业做好运行前的准备工作。例如建立相应的执行进程和分配系统资源; (4)作业运行结束的善后处理工作。 •作业调度算法: (1)先来先服务(FCFS) 作业平均周转时间=∑(作业完成时刻i-作业提交时刻i)/n个作业 (2)最短作业优先:在作业内容参差很不均衡时有合理性 (3)“响应比”最高的优先 “响应(系数)比”:作业响应时间(等待和运行)/作业运行时间 (4)定时轮转法(按时间片):适合作业不定的情况 (5)优先数法:急事先办的原则 第三章进程及处理机管理 1、为什么要引入“进程” (1)进程调度属于低级处理机管理,即确定系统中哪个进程将获得CPU;而作业调度属于高级处理机管理,即确定系统中哪些作业将获得CPU。 (2)进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。 (3)引入进程的意义是描述多道程序设计系统中程序的动态执行过程。 2、进程的定义及特征 (1)程序和进程的区别 (2)进程的五个基本特征:动态性、并发性、独立性、制约性、结构性 3、进程调度 (1)进程的三个基本状态及转换 三个基本状态是等待、执行和就绪,在一定的条件下,进程的状态将发生转换。 (2)进程调度算法 主要有先来先服务(FCFS)、时间片轮转法、多级反馈轮转法、优先数法。 (3)进程控制块(PCB)是进程存在的唯一标志,它描述了进程的动态性。 4、进程通信 (1)进程的同步与互斥 一般来说同步反映了进程之间的协作性质,往往指有几个进程共同完成一个任务时在时间次序上的某种限制,进程相互之间各自的存在及作用,通过交换信息完成通信。如接力比赛中一组队员使用接力棒等。 进程互斥体现了进程之间对资源的竞争关系,这时进程相互之间不一定清楚其它进程情况,往往指多个任务多个进程间的通讯制约,因而使用更广泛。如打篮球时双方挣抢篮板球等。 (2)临界区 并发进程中与共享资源有关的程序段定义为临界区。进入临界区的准则是:①一次只准一个进程进入临界区;②本进程结束负责通知下一进程;③进程调度,不能阻塞。 (3)原语 原语是不可中断的过程。 •加锁/开锁(LOCK/UNLOCK)原语 优点是实现互斥简单;缺点是效率很低。 •信号量(Semaphore)及PV操作 PV操作能够实现对临界区的管理要求。它由P操作原语和V操作原语组成,对信号量进行操作,具体定义如下: P(S):①将信号量S的值减1,即S=S-1; ②如果S 0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。 V(S):①将信号量S的值加1,即S=S+1; ②如果S>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。 信号量的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。注意信号量的值仅能由PV操作来改变。 一般来说,信号量S 0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个单位资源,因此S的值减1;当S<0时,表示已经没有可用资源,请求者必须等待别的进程释放该类资源,它才能运行下去。而执行一个V操作意味着释放一个单位资源,因此S的值加1;若S 0,表示有某些进程正在等待该资源,因此要唤醒一个等待状态的进程,使之运行下去。 •消息缓冲通信原语 高级通信原语,用于一组信息发送(Send)与读取(Read)。 5、死锁 (1)死锁的概念 死锁是两个或两个以上的进程中的每一个,都在等待其中另一个进程释放资源而被封锁,它们都无法向前推进,称这种现象为死锁现象。 产生死锁的原因是共享资源有限,多个进程对共享资源的竞争,而且操作不当。 (2)产生死锁的四个必要条件是资源互斥使用、保持和等待、非剥夺性、循环等待。 (3)解决死锁的方法 一般有死锁的预防,即破坏产生死锁的四个必要条件中的一个或多个,使系统绝不会进入死锁状态;死锁的避免,即在资源动态分配的过程中使用某种办法防止系统进人死锁状态;和允许系统产生死锁,然后使用检测算法及时地发现并解除它。 安全状态、安全系列、银行家算法等 第四章 存储管理 1、存储管理使用的基本概念 •逻辑地址与物理地址 在具有地址变换机构的计算机中,允许程序中编排的地址和信息实际存放在内存中的地址有所不同。前者叫逻辑(相对)地址,后者叫物理(绝对)地址。 •重定位:将逻辑地址转换为物理地址。 •虚拟存储管理 虚存是由操作系统调度,采用内外存的交换技术,各道程序在必需使用时调入内存,不用的调出内存,这样好象内存容量不受限制。 虚存的特点: (1)虚存容量不是无限的,极端情况受内存和外存可利用的总容量限制; (2)虚存容量还受计算机总线地址结构限制; (3)速度和容量的“时空”矛盾,虛存量的“扩大”是以牺牲CPU工作时间以及内外存交换时间为代价的。 •存储管理的目的及功能 目的是方便用户,提高内存资源的利用率,实现内存共享。 功能主要有内存的分配和管理、内存的扩充技术、内存保护技术 2、分区分配存储管理 分为固定分区、可变分区、可重定位分区、多重分区。 内存“扩充”技术: •交换:由操作系统做,用户不知道。 •覆盖:由用户控制,操作系统提供覆盖机制。 内存保护技术: ---保护系统工作区和用户作业区,特别是如何防止系统区被破坏。方法有存储保护键、界限寄存器 3、请求页式存储管理 (1)页式存储管理实现原理 基于程序在运行时不需要一开始都装入内存(局部性原理),更不应该把最近较长一段时间内不用的程序装入内存。 (2)页表的作用是将逻辑页号转换为物理块号。 (3)页面淘汰算法 先进先出算法(FIFO)、循环检测法、最近最少使用页面先淘汰(LRU)、最不经常使用的页面先淘汰(LFU)、最近没有使用页面先淘汰(NUR)、最优淘汰算法(OPT)等。 (4)页式存储管理的优、缺点 优点: •虛存量大,适合多道程序运行,用户不必担心内存不够的调度操作; •内存利用率高,不常用的页面尽量不留在内存; •不要求作业连续存放,有效地解决了“碎片”问题。与分区式相比,不需移动作业;与多重分区比,无零星碎片产生。 缺点: •要处理页面中断、缺页中断处理等,系统开销较大; •有可能产生“抖动”; •地址变换机构复杂,为提高速度采用硬件实现,增加了机器成本。 4、段式、段页式存储管理 段式、页式存储管理的对比。 段页式存储管理特点: •每一段分若干页,再按页式管理,页间不要求连续; •用分段方法分配管理作业,用分页方法分配管理内存; •兼有段式和页式管理的优点,系统复杂和开销增大,一般在大型机器上才使用。 第五章文件管理 1、文件管理任务与功能 任务:把存储、检索、共享和保护文件的手段,提供给操作系统本身和用户,以达到方便用户和提高资源利用率的目的。 功能: ---分配与管理外存 ---提供合适的存储方法 ---文件共享、保护,解决命名冲突 文件组织结构:文件、文件元素、文件系统 •文件系统 = 文件管理程序(文件和目录的集合)+ 它所管理的全部文件; •文件系统是用户与外存的接口; •为用户提供统一方法(以数据记录的逻辑单位),访问存储在物理介质上的信息。 2、文件分类 (1)按文件性质与用途分:系统文件、库文件、用户文件 (2)按操作保护分:只读文件、可读可写文件、可执行文件 (3)按使用情况分:临时文件、永久文件、档案文件 (4)按用户观点分:普通文件、目录文件、特殊文件 (5)按存取的物理结构分:顺序(连续)文件、链接文件、索引文件 (6)按文件的逻辑存储结构分:有结构文件、无结构文件 (7)按文件中的数据形式分:源文件、目标文件 3、文件的逻辑结构和物理结构 •文件的逻辑结构 ---从用户观点看 ---按文件名及记录号存取文件,是一维、连续的字符序列,方便存储、检索或加工 ---文件由若干个逻辑记录组成,并加以命名或编号 •文件的物理结构 又称文件的存储结构,是指文件在外存上的存储组织形式,是与存储介质的存储性能有关; 空闲空间的管理方法主要有:空闲表法、空闲(自由)链表法、成组链接法 4、文件目录 (1)文件目录分类:一级文件目录、二级文件目录、多级文件目录 (2)文件目录的管理 •目录做成文件,文件系统便于内部统一管理,目录文件在使用时调入内存; •在操作系统中,大量采用“表格”管理。 5、文件存取控制 •解决文件保护、保密和共享 •常用的文件存取控制方法有:存取控制矩阵、用户权限表、使用口令、使用密码 6、文件系统的数据结构和表示 UNIX或Linux操作系统中文件系统的主要特点 (1)操作系统文件的目录组织是一个树形结构,从根结点到叶子称为文件的全路径名,文件可以由其全路径名唯一确定; (2)文件本身是无结构的字符流; (3)把外部设备的特殊文件和普通文件以及目录文件都统一在文件这一概念上,对于一般文件的访问、共享和保护方式也可以适用于外部设备。 第六章 输入输出设备管理 1、设备管理的任务和功能 •设备管理的任务 (1)按用户需求提出的要求接入外部设备,系统按一定算法分配和管理控制,而用户不必关心设备的实际地址和控制指令; (2)尽量提高输入输出设备的利用率,例如发挥主机与外设以及外设与外设之间的真正并行工作能力。 •设备管理的功能 (1)分配设备 (2)控制和实现真正的输入输出操作 (3)对输入输出缓冲区进行管理 (4)在一些较大系统中实现虚拟设备技术 2、外部设备分类 (1)按系统和用户分:系统设备、用户设备 (2)按输入输出传送方式分(UNIX或Linux操作系统):字符型设备、块设备 (3)按资源特点分:独享设备、共享设备、虚拟设备 (4)按设备硬件物理特性分:顺序存取设备、直接存取设备 (5)按设备使用分:物理设备、逻辑设备、伪设备 •设备I/O方式:询问、通道、中断 •I/O设备分配算法:先来先服务(FCFS)、按优先级进行分配 3、设备管理技术 (1)I/O设置缓存理由 •解决信息的到达率和离去率不一致的矛盾; •缓存起中转站的作用; •使得一次输入的信息能多次使用; •在通道或控制器内设置局部寄存器作为缓冲存储器,可暂存I/O信息,以减少中断CPU的次数。这种情形可进一步推广,使得一次读入的信息可多次重复使用。 (2)虚拟设备的技术(SPOOLing) SPOOLing,即外围设备联机并行操作,它是关于慢速字符设备如何与计算机主机交换信息的一种技术,通常也叫做“假脱机技术”。是一种预输入、缓输出和转储的管理技术. SPOOLing系统的特点: •提高了I/O速度; •将独享设备改造为共享设备(典型例子是打印机的“共享”); •实现了虚拟设备功能。 4、设备处理程序编制内容 •设备驱动程序的功能 (1)将接收到的抽象要求转换为具体要求; (2)检查用户I/O请求的合法性,了解I/O设备的状态,传递有I/O关参数,设置设备的工作方式; (3)发出I/O命令,启动分配到的I/O设备,完成指定的I/O 操作; (4)及时响应由控制器或通道发来的中断请求,并根据其中断类型调用相应的中断处理程序进行处理; (5)对于设置有通道的计算机系统,驱动程序还应能够根据用户的 I/O请求,自动地构成通道程序。 •设备驱动程序的特点 (1)驱动程序主要是在请求I/O的进程与设备控制器之间的一个通信程序。 (2)驱动程序与I/O设备的特性紧密相关。 (3)驱动程序与I/O控制方式紧密相关。 (4)由于驱动程序与硬件紧密相关,因而其中的一部分程序用汇编语言书写,目前有很多驱动程序,其基本部分已经固化,放在ROM中。 •设备处理方式 (1)将抽象要求转换为具体要求 (2)检查I/O请求的合法性 (3)读出和检查设备的状态 (4)传送必要的参数 (5)方式的设置和I/O设备启动 难点分析 •如何理解操作系统在计算机系统中的地位? 操作系统是软件,而且是系统软件。它在计算机系统中的作用,大致可以从两方面体会:对内,操作系统管理计算机系统的各种资源,扩充硬件的功能;对外,操作系统提供良好的人机界面,方便用户使用计算机。它在整个计算机系统中具有承上启下的地位。 •系统调用与一般过程调用的区别。 系统调用在本质上是一种过程调用,但它是一种特殊的过程调用,它与一般过程调用的主要区别如下: (1)运行状态不同。一般的过程调用,其调用和被调用过程都是用户程序,它们都运行在同一系统状态下;而系统调用的调用过程是用户程序,它运行在用户态,其被调用过程是系统过程,运行在系统态。 (2)进入方式不同。一般过程调用可以直接通过过程调用语句将控制转移到被调用过程;而执行系统调用时,由于调用和被调用过程处于不同系统状态,必须通过访管中断进入。 (3)代码层次不同。一般过程调用中的被调用程序是用户级程序,而系统调用是操作系统中的代码程序,是系统级程序。 •下表给出作业l、2、3的提交时间和运行时间。采用先来先服务调度算法和短作业优先调度算法,试问平均周转时间各为多少?(时间单位:小时,以十进制进行计算。) 解:采用先来先服务调度策略,则调度顺序为l、2、3。  平均周转时间T=(8+11.6+12)/3=10.53 采用短作业优先调度策略,则调度顺序为l、3、2。  平均周转时间T=(8+8+12.6)/3=9.53 •试述文件管理系统设置打开文件、关闭文件命令的原因。 解:操作系统需要处理大量用户文件,而访问一个文件需要查询目录,有时甚至需要多次查询目录。由于文件目录与文件一起存放在辅存上,当存取文件时,必须先到辅存中读取文件目录信息,从中获得文件的存放地址,然后再去存取文件。这样一来,文件信息的存取将花费很多时间。如果将整个文件目录放入主存,虽然可以提高存取速度,但这需要占用大量主存空间,显然这也是不可取的。 实际上,在一段时间内使用的文件数总是有限的,因此只要将目录中当前要使用的那些文件的目录表目复制到内存中就可以了。这样既不占用太多的主存空间,又可显著提高查询文件目录的速度。为此,大多数操作系统中设置了两个文件操作:打开文件和关闭文件。 打开文件操作完成的功能是将文件的有关目录信息复制到主存活动文件表中,以建立用户和这个文件的联系。关闭文件操作的功能是用户宣布这个文件当前不再使用,系统将其在主存中的相应目录信息删去,因而也就切断了用户同这个文件的联系。 •有一个文件系统如图(a)所示,图中的框表示目录,圈表示普通文件。根目录常驻内存,目录文件组织成链接文件,不设文件控制块,普通文件组织成索引文件。目录表目指示下一级文件名及其磁盘地址(各占2个字节,共4个字节)。若下级文件是目录文件,指示其第一个磁盘块地址。若下级文件是普通文件,指示其文件控制块的磁盘地址。每个目录文件磁盘块最后4个字节供拉链使用。下级文件在上级目录文件中的次序在图中为从左至右。每个磁盘块有512字节,与普通文件的一页等长。 普通文件的文件控制块组织结构如图(b)所示,其中每个磁盘地址占2个字节,前10个地址直接指示该文件前10页的地址。第11个地址指示一级索引表地址,一级索引表中每个磁盘地址指示一个文件页地址;第12个地址指示二级索引表地址,二级索引表中每个地址指示一个一级索引表地址;第13个地址指示三级索引表地址,三级索引表中每个地址指示一个二级索引表地址。问: (1)一个普通文件最多可有多少个文件页? (2)若要读文件J中的某一页,最多启动磁盘多少次? (3)若要读文件W中的某一页,最少启动磁盘多少次? 答:(1)由题目中所给条件可知,磁盘块大小为512字节,每个磁盘地址占2个字节。因此,一个一级索引表可容纳256个磁盘地址。同样地,一个二级索引表可容纳256个一级索引表地址,一个三级索引表可容纳256个二级索引表地址。这样,一个普通文件最多可有页数为:10+256+256&times;256+256&times;256&times;256=16843018 (2)从图(a)中可以看出,目录文件A和目录文件D中的目录项都只有两个,因此这两个目录文件都不需要拉链。若要读文件J中的某一项,首先从内存的根目录中找到目录文件A的磁盘地址,将其读入内存(第1次访问磁盘)。然后再从目录A中找出目录文件D的磁盘地址,并将其读入内存(第2次访问磁盘)。从目录D中找出文件J的文件控制块地址,将文件J的文件控制块读入内存(第3次访问磁盘)。在最坏情况下,要访问页的磁盘地址需通过三级索引才能找到,这时要三次访问磁盘才能将三级索引表读入内存(第4、5、6次访问磁盘)。最后读入文件J中的相应页(第7次访问磁盘)。 由此可知,若要读文件J中的某一页,最多启动磁盘7次。 (3)从图(a)中可以看出,目录文件C和目录文件U中,目录项数目较多,若目录项数超过127(512/4-l=127),则目录文件的读入可能需要多次磁盘读(因目录文件组织成链接文件)。在最好情况下,所找的目录项都在目录文件的第一个磁盘块中。若要读文件W中的某一页,首先从内存的根目录中找到目录文件C的磁盘地址,将其读入内存(第1次访问磁盘)。在最好情况下,能从目录C的第一个磁盘块中找出目录文件互的磁盘地址,并将其读入内存(第2次访问磁盘)。从目录I中找出目录文件P的的磁盘地址,将其读入内存(第3次访问磁盘)。从目录P中找到目录文件U的磁盘地址,将其读入内存(第4次访问磁盘)。在最好情况下,能从目录U的第一个磁盘块中找出文件W的文件控制块地址,将文件W的文件控制块读入内存(第5次访问磁盘)。在最好情况下,要访问的页在前10页中,这时可直接得到该页的磁盘地址。最后读入文件W中的相应页(第6次访问磁盘)。 由此可知,若要读文件W中的某一页,最少启动磁盘6次。 •采用可变分区管理存储空间时,若主存中按地址顺序依次有五个空闲区,大小分别为15K、28K、10K、226K、110K。现有五个作业J1到J5,它们所需的主存空间依次是10K、15K、102K、26K、180K。问如果采用首次适应分配算法,能否把这五个作业按J1到J5的次序全部装入主存。使用哪种分配算法装入这五个作业,可使主存的利用率最高? 解:按首次适应分配算法,不能把这五个作业全部依次装入主存。这时J1、J2装入第1、2个空闲区,J3、J4装入第4、5个空闲区,J5有180K,无法装入仅有的10K空闲区。 能使主存利用率最高的是采用最佳适应分配算法。这时,这五个空闲块分别装入作业J2、J4、J1、J5、J3。 •某虚拟存储器的用户编程空间共32个页面,每页为1KB,内存为16KB。假定某时刻一用户页表中已调入内存的页面的页号和物理块号的对照表如下: 请计算逻辑地址0A5C(H)所对应的绝对地址。 解:页式存储管理的逻辑地址分为两部分:页号和页内地址。由已知条件“用户编程空间共32个页面”,可知页号部分占5位;由“每页为1KB”,1K=210,可知内页地址占10位。由“内存为16KB”,可知有16块,块号为4位。 逻辑地址0A5C(H)所对应的二进制表示形式是:000 1010 0101 1100 ,根据上面的分析,下划线部分为页内地址,编码 “000 10” 为页号,表示该逻辑地址对应的页号为2。查页表,得到物理块号是4(十进制),即物理块地址为:01 00 ,拼接块内地址10 0101 1100,得01 0010 0101 1100,即125C(H)。 •某采用页式存储管理的系统,接收了一个共7页的作业,作业执行时依次访问的页为:1、2、3、4、2、1、5、6、2、1、2、3、7。当内存块数量为4时,请分别用先进先出(FIFO)调度算法和最近最少使用(LRU)调度算法,计算作业执行过程中会产生多少次缺页中断?写出依次产生缺页中断后应淘汰的页。(所有内存开始时都是空的,凡第一次用到的页面都产生一次缺页中断。要求写出计算过程) 解:(1)采用先进先出(FIFO)调度算法,页面调度过程如下: 所以,共产生10次缺页中断,依次淘汰的页是1、2、3、4、5、6。 (2)采用最近最少使用(LRU)调度算法,页面调度过程如下: 因此,共产生8次缺页中断,依次淘汰的页是3、4、5、6。 •试述分页式存储管理系统和分段式存储管理系统的主要区别。 解:分页和分段有许多相似之处,比如两者都不要求作业连续存放。但在概念上两者完全不同,主要表现在以下几个方式: (1)页是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,或者说分页是由于系统管理的需要。段是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地实现共享,满足用户的需要。 (2)页的大小固定,由系统确定,将逻辑地址划分为页号和页内地址是由机器硬件实现的。而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划分。 (3)分页的作业地址空间是一维的。分段的地址空间是二维的。 •为什么说有了通道技术和中断技术才真正做到了CPU与外设的并行操作? 解:通道是负责外围设备与主存之间进行数据交换,能单独完成输入输出操作的处理机。有了通道,主存和外围设备之间的数据交换就不要CPU干预了,CPU可以做与输入输出无关的其他工作,从而使计算机系统获得了CPU与外围设备之间并行工作的能力。 I/O中断是通道和CPU协调工作的一种手段。如果没有中断技术,CPU就要不断去查询通道以及设备执行的情况,这样一来,CPU还是把大量的时间花在了查询上,不能很好地为其他进程服务。使用中断技术,CPU可以完全不管通道和设备的执行情况,因为无论操作正常结束或操作异常结束,通道都会发出中断,通知CPU来处理。 综上所述,通道技术和中断技术的出现,使得主存可以直接与外设交换数据,而CPU得以并行地工作,大大提高了CPU的使用效率。 •某分时系统的进程出现如图所示的状态变化。 试问:(1)你认为该系统采用的是何种进程调度算法? (2)把图中所示的六个状态变化的原因写出来。 解:(1)该分时系统采用的进程调度算法是时间片轮转法。 (2)①进程被选中,变成运行态;②时间片到,运行的进程排入就绪队列尾部;③运行的进程启动打印机,等待打印;④打印工作结束,等待的进程排入就绪队列尾部;⑤等待磁盘读文件工作;⑥磁盘传输信息结束,等待的进程排入就绪队列尾部。 •怎样理解操作系统的作业调度和进程调度的关系? 解:作业调度和进程调度都属于处理机调度。作业调度是处理机管理的高级形式,它的主要功能是审查系统是否能满足用户作业的资源要求以及按照一定的算法来选取作业。进程调度是处理机管理的低级形式,它的主要功能是根据一定的算法将CPU分派给就绪队列中的一个进程。 作业的状态及其转换 操作系统中作业的状态主要有:提交、后备、执行、完成,进程的状态主要有等待、就绪、执行。作业调度和进程调度的转换关系见下图。 •用PV操作实现进程间的同步与互斥应该注意什么? 解:用PV操作实现进程间的同步与互斥,应该注意以下四方面问题: (1)对每一个共享资源都要设立信号量。互斥时对一个共享资源设立一个信号量;同步时对一个共享资源可能要设立两个或多个信号量,要视由几个进程来使用该共享变量而定。 (2)互斥时信号量的初值一般为1;同步时至少有一个信号量的初值大于等于1。 (3)PV操作一定要成对调用。互斥时在临界区前后对同一信号量作PV操作;同步时则对不同的信号量作PV操作,PV操作的位置一定要正确。 (4)对互斥和同步混合问题,PV操作可能会嵌套,一般同步的PV操作在外,互斥的PV操作在内。 三、课程练习及参考解答 一、填空 1、设备I/O方式有如下三种:_________、__________和___________。 2、文件存取方式按存取次序通常分_________________、_______________,还有一类 ______________。 3、从用户观点看,UNIX系统将文件分三类:___________________、___________________和 _________________。 4、引起死锁的四个必要条件是 、________________、 和__________________。 5、进程的三个最基本状态是_____________、____________和_____________。 6、传统操作系统提供编程人员的接口称为________________。 7、三代人机界面的发展是指:______________、_________________和_______________。 8、常用的进程调度算法有_________________、_________________和___________________。 二、选择一个正确答案的序号填入括号中 1、计算机操作系统是一个( )。 A. 应用软件 B. 硬件的扩充 C. 用户软件 D.系统软件 2、操作系统程序结构的主要特点是( )。 A. 一个程序模块 B. 分层结构 C. 层次模块化结构 D. 子程序结构 3、面向用户的组织机构属于( )。 A. 虚拟结构 B. 逻辑结构 C. 实际结构 D. 物理结构 4、操作系统中应用最多的数据结构是( )。 A. 堆栈 B. 队列 C. 表格 D. 树 5、可重定位内存分区分配目的为( )。 A. 解决碎片问题 B. 便于多作业共享内存 C. 回收空白区方便 D. 摆脱用户干预 6、逻辑地址就是( )。 A. 用户地址 B. 相对地址 C. 物理地址 D.绝对地址 7、原语是( )。 A. 一条机器指令 B. 若干条机器指令组成 C. 一条特定指令 D. 中途能打断的指令 8、索引式(随机)文件组织的一个主要优点是( )。 A. 不需要链接指针 B. 用户存取方便 C.回收实现比较简单 D.能实现物理块的动态分配 9、几年前一位芬兰大学生在Internet上公开发布了以下一种免费操作系统核心( ),经过许多人的努力,该操作系统正不断完善,并被推广。 A. Windows NT B. Linux C. UNIX D. OS2 10.文件目录的主要作用是( )。 A. 按名存取 B.提高速度 C.节省空间 D.提高外存利用率 11、某进程在运行过程中需要等待从磁盘上读入数据,此时该进程的状态是( )。 A. 从就绪变为运行 B.从运行变为就绪 C. 从运行变为阻塞 D.从阻塞变为就绪 12、把逻辑地址转变为内存的物理地址的过程称作( )。 A.编译 B.连接 C.运行 D.重定位 13、进程和程序的一个本质区别是( )。 A.前者分时使用CPU, 后者独占CPU B.前者存储在内存,后者存储在外存 C.前者在一个文件中,后者在多个文件中 D.前者为动态的,后者为静态的 三、是非题,正确的在括号内划√,错的划&times;。 ( )1、进程间的相互制约关系体现为进程的互斥和同步。 ( )2、只有一个终端的计算机无法安装多用户操作系统。 ( )3、UNIX的最大特点是分时多用户、多任务和倒树型文件结构。 ( )4、常用的缓冲技术有双缓冲,环形缓冲和缓冲池。 ( )5、实时操作系统的响应系数最小,设备利用率最差。 ( )6、死锁是指两个或多个进程都处于互相等待状态而无法继续工作。 ( )7、具有多道功能的操作系统一定是多用户操作系统。 ( )8、一般的分时操作系统无法做实时控制用。 ( )9、多用户操作系统在单一硬件终端硬件支持下仍然可以工作。 ( )10、常用的缓冲技术是解决慢速设备与快速CPU处理之间协调工作。 四、回答题 1、试以生产者——消费者问题说明进程同步问题的实质。 2、以一台打印机为例,简述SPOOLing 技术的优点。 3、简述请求页式存储管理的优缺点。 4、虚拟存储器的基本特征是什么?虚拟存储器的容量主要受到什么限制? 5、现代操作系统与传统操作系统相比,设计中采用了哪些先进技术? 练习参考解答 一、填空 1、询问、中断、通道 2、顺序存取、直接存取、按键索引 3、普通(用户)、目录、特殊 4、互斥使用、保持和等待、非剥夺性、循环等待 5、准备(就绪)、执行、等待 6、系统调用 7、一维命令行、二维图形界面、三维虚拟现实 8、先来先服务、优先数法、轮转法 二、选择题 1、D 2、C 3、B 4、C 5、A 6、B 7、B 8、D 9、B 10、A 11、C 12、D 13、D 三、是非题 有错误的是第2、5、7题,其余均是正确的。 四、回答题 1、答:一个生产者,一个消费者和一个产品之间关系是典型的进程同步问题。设信号量S为仓库内产品,P-V操作配对进行缺一不可。生产者进程将产品放入仓库后通知消费者可用;消费者进程在得知仓库有产品时取走,然后告诉生产者可继续生产。 2、答:以一台打印机为例, SPOOLing 技术的主要优点是在多用户情况下,每一个用户使用打印机就好像自己拥有一台打印机。不会产生打印机“忙”而等待。 3、答:优点: (1)虛存量大,适合多道程序运行,用户不必担心内存不够的调度操作。动态页式管理提供了内存与外存统一管理的虚存实现方式。 (2)内存利用率高,不常用的页面尽量不留在内存。 (3)不要求作业连续存放,有效地解决了“碎片”问题。与分区式比,不需移动作业;与多重分区比,无零星碎片产生。UNIX操作系统较早采用。 缺点: (1)要处理页面中断、缺页中断处理等,系统开销较大。 (2)有可能产生“抖动”。 (3)地址变换机构复杂,为提高速度采用硬件实现,增加了机器成本。 4、答:虚存是由操作系统调度,采用内外存的交换技术,各道程序在必需使用时调入内存,不用的调出内存,这祥好像内存容量不受限制。但要注意: (1)虚存容量不是无限的,极端情况受内存、外存的可使用的总容量限制; (2)虚存容量还受计算机总线长度的地址结构限制; (3)速度和容量的“时空”矛盾,虛存量的“扩大”是以牺牲CPU工作时间以及内、外存交换时间为代价的。 5、答:现代操作系统是指网络操作系统和分布式操作系统,采用了网络地址方案、网络协议、路由技术和微内核等先进技术。
特性 • 带一个、两个、三个或四个通道的上桥臂流监 视器 - 流检测压的满量程范围为 100 mV,分辨 率为16位 - 可选双向流检测功能,范围为 -100 mV 到 +100 mV, 16位二进制补码(有符号)数据 格式 - 外部检测阻可设置满量程流范围 - 输入流极低,可简化布线 • 压监视器的总线压范围宽 - 输入共模压为0V到32V - 压测量分辨率为 16 位;功率计算使用的分 辨率为14位 • 实时自动校准压和流的失调和增益误差,无需 用户调整 • 宽动态范围内的功率测量精度达1% • 在片上对 28 位功率结果进行累加,从而实现能量 测量 - 48 位功率累加器寄存器,用于记录累加的功 率数据 - 24位累加器计数 - 用户可编程采样率为每秒8、 64、 256和1024次 采样 - 支持 17 分钟的功率数据累加,最小采样率 为1024次/秒 - 支持超过36小时的功率数据累加,最小采样 率为8次/秒 • 2.7V到5.5V工作源 - 用于数字I/O的单独VDD I/O引脚 - SMBus和数字I/O支持1.62-5.5V压 - SMBus 3.0和I2C增强型快速模式(1 Mbps) • SMBus地址——通过阻设置16种选项 • 不需要输入滤波器 • 可使能的报警功能: - 在累加器溢出时发出报警 - 转换完成时发出报警 • 4 &times; 4 &times; 0.5 mm UQFN封装 • 2.225 &times; 2.17 mm WLCSP封装 应用 • 嵌入式计算 • 网络 • FPGA系统 • 汽车级 • 低压/大功率—— AI和GPU • 工业级 • Linux®应用 • 笔记本脑和平板脑计算 • 云、 Linux和服务器计算 • 光学网络模块 计算平台支持 • Windows® 10驱动程序 • Linux驱动程序 • Python™脚本 说明 PAC1931/2/3/4是单通道、双通道、三通道和四通道 源和能量监视器件。高压多路开关按顺序将输入连接到 为高分辨率ADC馈总线压监视器和流检测放 大器。数字路执行功率计算和能量累加,以便在 1 ms 到36 小时或更长时间的积分周期内进行能量监 视。总线压、检测压和累加的比例功率存储在 寄存器中,以供系统主器件或嵌入式控制器检索。 采样率和能量积分周期可通过SMBus或I2C控制。工作 通道选择、单触发测量和其他控制也可通过SMBus或 I2C配置。 PAC1931/2/3/4器件系列使用实时校准,可最大限度地 减少失调和增益误差,并且无需使用输入滤波器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值