字节对齐详解

一.什么是字节对齐,为什么要对齐?

    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
    对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。

二.字节对齐对程序的影响:

    先让我们看几个例子吧(32bit,x86环境,gcc编译器):
设结构体如下定义:
struct A
{
    int a;
    char b;
    short c;
};
struct B
{
    char b;
    int a;
    short c;
};
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号无符号同)    
short:2(有符号无符号同)    
int:4(有符号无符号同)    
long:4(有符号无符号同)    
float:4    double:8
那么上面两个结构大小如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12

结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。
之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct C)值是8。
修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct D)值为7。
后面我们再讲解#pragma pack()的作用.

三.编译器是按照什么样的原则进行对齐的?

    先让我们看四个重要的基本概念:
1.数据类型自身的对齐值:
  对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
有 了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是 表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数 据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数 倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。
例子分析:
分析例子B;
struct B
{
    char b;
    int a;
    short c;
};
假 设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定 对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4, 所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为 2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的 都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B 共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了, 因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那 么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一 个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其 自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只 是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
同理,分析上面例子C:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第 一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1= 0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续 字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0x0006、0x0007中,符合 0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C 只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.

四.如何修改编译器的默认对齐值?

1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.

五.针对字节对齐,我们在编程中如何考虑?


    如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照 类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做 法是显式的插入reserved成员:
         struct A{
           char a;
           char reserved[3];//使用空间换时间
           int b;
}

reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.

六.字节对齐可能带来的隐患:

    代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;

p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.

七.如何查找与字节对齐方面的问题:

如果出现对齐或者赋值问题首先查看
1. 编译器的big little端设置
2. 看这种体系本身是否支持非对齐访问
3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。

八.相关文章:转自http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx

 ARM下的对齐处理 
from DUI0067D_ADS1_2_CompLib

3.13 type  qulifiers

有部分摘自ARM编译器文档对齐部分

对齐的使用:
1.__align(num)
   这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时
   就要用到此命令__align(8)进行修饰限制。来保证数据对象是相应对齐。
   这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节
   对齐,但是不能让4字节的对象2字节对齐。
   __align是存储类修改,他只修饰最高级类型对象不能用于结构或者函数对象。
   
2.__packed 
  __packed是进行一字节对齐
  1.不能对packed的对象进行对齐
  2.所有对象的读写访问都进行非对齐访问
  3.float及包含float的结构联合及未用__packed的对象将不能字节对齐
  4.__packed对局部整形变量无影响
  5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定
  义为packed。
     __packed int* p;  //__packed int 则没有意义
  6.对齐或非对齐读写访问带来问题
  __packed struct STRUCT_TEST
 {
  char a;
  int b;
  char c;
 }  ;    //定义如下结构此时b的起始地址一定是不对齐的
         //在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL]
//将下面变量定义成全局静态不在栈上 
static char* p;
static struct STRUCT_TEST a;
void Main()
{
 __packed int* q;  //此时定义成__packed来修饰当前q指向为非对齐的数据地址下面的访问则可以

 p = (char*)&a;          
 q = (int*)(p+1);      
 
 *q = 0x87654321; 
/*   
得到赋值的汇编指令很清楚
ldr      r5,0x20001590 ; = #0x12345678
[0xe1a00005]   mov      r0,r5
[0xeb0000b0]   bl       __rt_uwrite4  //在此处调用一个写4byte的操作函数 
      
[0xe5c10000]   strb     r0,[r1,#0]   //函数进行4次strb操作然后返回保证了数据正确的访问
[0xe1a02420]   mov      r2,r0,lsr #8
[0xe5c12001]   strb     r2,[r1,#1]
[0xe1a02820]   mov      r2,r0,lsr #16
[0xe5c12002]   strb     r2,[r1,#2]
[0xe1a02c20]   mov      r2,r0,lsr #24
[0xe5c12003]   strb     r2,[r1,#3]
[0xe1a0f00e]   mov      pc,r14
*/

/*
如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败
[0xe59f2018]   ldr      r2,0x20001594 ; = #0x87654321
[0xe5812000]   str      r2,[r1,#0]
*/

//这样可以很清楚的看到非对齐访问是如何产生错误的
//以及如何消除非对齐访问带来问题
//也可以看到非对齐访问和对齐访问的指令差异导致效率问题
}
 

Linux-2.6.21.1 网络函数调用流程

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。 

  1. 接收以太帧:  
  2. netif_rx  
  3.   -> queue  
  4.   -> netif_receive_skb  
  5.     -> bond  
  6.  -> packet_type_all: deliver_skb  
  7.  -> bridge  
  8.  -> packet_type(IPV4)->func == ip_rcv  
  9.    
  10. 接收IPv4包:  
  11. ip_rcv  
  12.   -> NF_HOOK(PREROUTING)  
  13.     ->ip_rcv_finish  
  14.       -> ip_route_input  
  15.         -> ip_route_input_cached  
  16.           -> ip_route_input_slow  
  17.             -> ip_mkroute_input  
  18.               -> __mkroute_input  
  19.                 dst->input = ip_forward  
  20.                 dst->output = ip_output  
  21.       -> dst_input  
  22.         -> LOCAL_IN: dst->input == ip_local_deliver  
  23.           -> NF_HOOK(NF_INPUT)  
  24.             -> ip_local_deliver_finish  
  25.               -> ipprot->handler(tcp, udp, icmp ...)  
  26.         -> FORWARD:  dst->input == ip_forward  
  27.     
  28. 转发:  
  29.     
  30. ip_forward  
  31.   -> xfrm4_route_forward (net/xfrm.h, get xfrm_dst)  
  32.     -> xfrm_route_forward  
  33.       -> __xfrm_route_forward  
  34.         -> xfrm_lookup  
  35.           -> xfrm_find_bundle  
  36.             -> afinfo->find_bundle == __xfrm4_find_bundle  
  37.           -> xfrm_bundle_create  
  38.             -> afinfo->bundle_create == __xfrm4_bundle_create  
  39.               tunnel mode  
  40.               -> xfrm_dst_lookup  
  41.                 -> afinfo->dst_lookup == xfrm4_dst_lookup  
  42.                   -> __ip_route_output_key  
  43.           -> dst_list: dst->list=policy_bundles, policy->bundles = dst  
  44.   -> NF_HOOK(NF_FORWARD)  
  45.   -> ip_forward_finish  
  46.   -> dst_output  
  47.    
  48. 输出:  
  49. icmp:  
  50. icmp_send  
  51.   -> ip_route_output_key  
  52.     -> ip_route_output_flow  
  53.   -> icmp_push_reply  
  54.     -> ip_append_data  
  55.  -> skb_queue_walk  
  56.     -> ip_push_appending_frames  
  57.    
  58. tcp:  
  59. tcp_connect  
  60.   -> ip_route_connect  
  61.     -> ip_route_output_flow  
  62. tcp_sendmsg  
  63.   -> __tcp_push_appending_frames  
  64.     -> tcp_write_xmit  
  65.    -> tcp_transmit_skb  
  66.         -> net_xmit_eval  
  67.           -> icsk->icsk_af_ops->queue_xmit == ipv4_specific->queue_xmit == ip_queue_xmit  
  68.   -> tcp_push_one  
  69.     -> tcp_transmit_skb  
  70.       -> net_xmit_eval  
  71.         -> icsk->icsk_af_ops->queue_xmit == ipv4_specific->queue_xmit == ip_queue_xmit  
  72.    
  73. tcp_protocol->handler == tcp_v4_rcv  
  74.   -> __inet_lookup  
  75.   -> xfrm_policy_check  
  76.   -> tcp_v4_do_rcv  
  77.     -> tcp_rcv_state_process  
  78.       -> icsk->icsk_af_ops->conn_request == tcp_v4_conn_request  
  79.         -> tcp_v4_send_synack  
  80.           -> ip_build_and_send_pkt  
  81.             -> NF_HOOK( NF_OUTPUT )  
  82.               -> dst_output  
  83.   
  84. udp:  
  85. udp_sendmsg  
  86.   -> ip_route_output_flow  
  87.   -> ip_append_data  
  88.     -> __skb_queue_tail( sk_write_queue )  
  89.   -> udp_push_pending_frames  
  90.     -> ip_push_pending_frames  
  91. raw:  
  92. raw_sendmsg  
  93.   -> ip_route_output_flow  
  94.   -> ip_append_data  
  95.     -> __skb_queue_tail( sk_write_queue )  
  96.   -> ip_push_pending_frames  
  97.   
  98. ip_push_pending_frames  
  99.   -> __skb_dequeue(sk_write_queue)  
  100.     -> NF_HOOK(NF_OUTPUT)  
  101.       -> dst_output  
  102.   
  103. ip_queue_xmit  
  104.   -> ip_route_output_flow  
  105.     -> xfrm_lookup  
  106.       -> xfrm_find_bundle  
  107.         -> bundle_create  
  108.           -> afinfo->bundle_create == __xfrm4_bundle_create  
  109.             -> xfrm_dst_lookup  
  110.               -> afinfo->dst_lookup == xfrm4_dst_lookup  
  111.                 -> __ip_route_output_key  
  112.         -> dst_list  
  113.         -> dst->list=policy_bundles, policy->bundles = dst  
  114.   -> NF_HOOK(NF_OUTPUT)  
  115.   -> dst_output  
  116.     -> dst->output  
  117.    
  118. dst_output: dst_list循环  
  119.   -> dst->output == xfrm_dst->output == xfrm4_output == xfrm4_state_afinfo->output  
  120.     -> NF_HOOK(POSTROUTING)  
  121.       -> xfrm4_output_finish  
  122.         -> gso ?  
  123.         -> xfrm4_output_finish2  
  124.           -> xfrm4_output_one  
  125.             -> mode->output  
  126.             -> type->output  
  127.             -> skb->dst=dst_pop(skb->dst)  
  128.           -> nf_hook(NF_OUTPUT)  
  129.             -> !dst->xfrm  
  130.               -> dst_output  
  131.           -> nf_hook(POSTROUTING)  
  132.   -> dst->output == ip_output  
  133.     -> NF_HOOK(POSTROUTING)  
  134.       -> ip_finish_output  
  135.         -> ip_finish_output2  
  136.           -> hh_output == dev_queue_xmit  

随处可见的system()函数

linux的system () 函数详解

system(执行shell 命令)
相关函数
        fork,execve,waitpid,popen
表头文件
        #i nclude<stdlib.h>
定义函数
        int system(const char * string);
函数说明
        system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命>令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
返回值
=-1:出现错误 
=0:调用成功但是没有出现子进程 
>0:成功退出的子进程的id
        如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值>。
如果system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为 system()调用/bin/sh失败所返回的127,因此最好能再检查errno 来确认执行成功。
附加说明
        在编写具有SUID/SGID权限的程序时请勿使用system(),system()会继承环境变量,通过环境变量可能会造成系统安全的问题。
范例
        #i nclude<stdlib.h>
main()
{
system(“ls -al /etc/passwd /etc/shadow”);
}
执行结果:

-rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd
-r--------- 1 root root 572 Sep 2 15 :34 /etc/shado

例2:

char tmp[];
sprintf(tmp,"/bin/mount -t vfat %s /mnt/usb",dev);
system(tmp);
其中dev是/dev/sda1。

linux设备模型

Overview

Linuxsysfs文件系统一般mount/sys目录。本文主要介绍sysfs文件系统中设备驱动模型的建立过程,内核版本2.6.29

设备驱动信息主要用来表示设备以及驱动的层次关系,以及处理热插拔等。/sys中与之相关的数据有:

class              代表一类设备,比如mtdnettty等等

bus         总线,比如PCIUSBI2C

device     代表一个设备

driver      代表一个驱动

 

以下是一些sysfs中的全局变量:

//  /sys/class

struct kset * class_kset = kset_create_and_add("class", NULL, NULL);                 

//     /sys/bus

struct kset * bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);    

//     /sys/devices

struct kset * devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);   

 

1.     Class

1.1           class的基本结构

struct class {

         const char            *name;

         struct module               *owner;

         struct class_attribute             *class_attrs;

         struct device_attribute           *dev_attrs;

         struct kobject                         *dev_kobj;

         int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);

         void (*class_release)(struct class *class);

         void (*dev_release)(struct device *dev);

         int (*suspend)(struct device *dev, pm_message_t state);

         int (*resume)(struct device *dev);

         struct pm_ops *pm;

         struct class_private *p;

};

struct class_private {

         struct kset class_subsys;

         struct list_head class_devices;

         struct list_head class_interfaces;

         struct kset class_dirs;

         struct mutex class_mutex;

         struct class *class;

};

classsysfs中的层次由struct class_private决定。struct class只是class_private的封装。

struct class_private::class_subsys.kobj.kset = class_kset;  // 父目录为/sys/class

struct class_private::class_subsys.kobj->name代表这个class/sys/class中显示的名字

struct class::dev_attrs为设备属性,往class中添加设备的时候,这些属性会自动添加到设备目录下。

1.2           新建class

新建class有两种方法:静态创建和动态创建。

l         静态创建

static struct class i2c_adapter_class = {

       .owner                  = THIS_MODULE,

       .name                    = "i2c-adapter",

       .dev_attrs              = i2c_adapter_attrs,

       .class_attrs   = ...

};

int retval = class_register(&i2c_adapter_class)

 

l         动态创建

i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");

class_create分配申请一块空间给class然后对nameownerclass_release函数赋值并最终调用class_register

class_register

根据struct class的值,设置struct class_private

调用add_class_attrsclass中添加属性。

l         class attrs

class的属性最终是在/sys/class/<new class>/目录下以文件的形式存在。用户程序可以直接对这些属性进行读写。如果要静态创建属性,可以在定义class时对.class_attrs域赋值使其指向要添加的attr数组。如果要动态创建。可以通过函数class_create_file添加。

int class_create_file(struct class *cls, const struct class_attribute *attr);

 

如果是动态创建的属性,需要在模块卸载时调用class_remove_file释放。

如果是静态创建的属性,在调用class_unregister时会自动释放。

2.     Bus

2.1 bus的基本结构

struct bus_type {

         const char            *name;

         struct bus_attribute      *bus_attrs;

         struct device_attribute *dev_attrs;

         struct driver_attribute  *drv_attrs;

         int (*match)(struct device *dev, struct device_driver *drv);

         int (*uevent)(struct device *dev, struct kobj_uevent_env *env);

         int (*probe)(struct device *dev);

         int (*remove)(struct device *dev);

         void (*shutdown)(struct device *dev);

         int (*suspend)(struct device *dev, pm_message_t state);

         int (*suspend_late)(struct device *dev, pm_message_t state);

         int (*resume_early)(struct device *dev);

         int (*resume)(struct device *dev);

         struct pm_ext_ops *pm;

         struct bus_type_private *p;

};

struct bus_type_private {

         struct kset subsys;

         struct kset *drivers_kset;

         struct kset *devices_kset;

         struct klist klist_devices;

         struct klist klist_drivers;

         struct blocking_notifier_head bus_notifier;

         unsigned int drivers_autoprobe:1;

         struct bus_type *bus;

};

class类似,bussysfs中的显示由struct bus_type_private决定,struct bus_type只是一个封装。

struct bus_type_private::subsys.kobj代表/sys/bus/<bus>目录。

struct bus_type_private::subsys.kobj.kset = bus_kset; // 默认父目录为/sys/bus/

struct bus_type_private::subsys.kobj.ktype = &bus_ktype;  // bus的属性操作

struct bus_type_private::subsys.kobj.name = <bus/sys/bus/目录下显示的名字>;

/sys/bus/目录,每创建成功一个<bus>,都会自动创建两个子目录driversdevices,分别代表连到此<bus>的设备和驱动。在driversdevices子目录下,每新建一个driver,会把struct bus_type中的drv_attrs属性赋给那个driver;每创建一个device,会把struct bus_typedev_attrs赋给那个device

2.2 新建bus

struct bus_type i2c_bus_type = {

       .name             = "i2c",

       .dev_attrs              = i2c_dev_attrs,

       .match           = i2c_device_match,

       .uevent           = i2c_device_uevent,

       .probe            = i2c_device_probe,

       .remove          = i2c_device_remove,

       .shutdown      = i2c_device_shutdown,

       .suspend         = i2c_device_suspend,

       .resume          = i2c_device_resume,

       .bus_attr         = …

};

int ret = bus_register(&i2c_bus_type);

 

int bus_register(struct bus_type *bus);

分配内存给struct bus_type_private;

根据struct bus_type的域设置bus_type_private;

根据.bus_attr设置bus的属性,这些属性在bus_unregister时会被自动释放。(bus属性也可通过bus_create_file动态添加,但所有动态添加的属性都要在卸载时通过bus_remove_file释放。)

 

3Device

3.1 Device的基本结构

struct device {

         struct klist           klist_children;

         struct klist_node knode_parent;     /* node in sibling list */

         struct klist_node knode_driver;

         struct klist_node knode_bus;

         struct device                 *parent;

         struct kobject kobj;

         char   bus_id[BUS_ID_SIZE];        /* position on parent bus */

         const char            *init_name; /* initial name of the device */

         struct device_type       *type;

         unsigned              uevent_suppress:1;

         struct semaphore          sem;  /* semaphore to synchronize calls to its driver.         */

         struct bus_type  *bus;          /* type of bus device is on */

         struct device_driver *driver;  /* which driver has allocated this device */

         void            *driver_data;       /* data private to the driver */

         void            *platform_data;  /* Platform specific data, device core doesn't touch it */

         struct dev_pm_info      power;

         u64             *dma_mask;        /* dma mask (if dma'able device) */

         u64             coherent_dma_mask;/* Like dma_mask, but for alloc_coherent mappings as not all hardware supports 64 bit addresses for consistent allocations such descriptors. */

         struct device_dma_parameters *dma_parms;

         struct list_head   dma_pools;         /* dma pools (if dma'ble) */

         struct dma_coherent_mem    *dma_mem; /* internal for coherent mem

                                                    override */

         /* arch specific additions */

         struct dev_archdata      archdata;

         spinlock_t           devres_lock;

         struct list_head   devres_head;

         struct list_head   node;

         struct class          *class;

         dev_t                            devt; /* dev_t, creates the sysfs "dev" */

         struct attribute_group  **groups;   /* optional groups */

         void  (*release)(struct device *dev);

};

3.2 device_register

int device_register(struct device *dev) ;

此函数将device登记到sysfs中。在调用之前,需对struct device进行初始化。

struct device dev ;

dev. parent = <parent dev> ;  // 父设备

dev.release = <func release> ; // 登记释放dev时调用的回调函数

dev.class = <class> ;     // struct class

dev.bus = <bus> ;         // 所属总线

然后调用device_register(&dev) ;

int device_register(struct device *dev)

{

       device_initialize(dev);

       return device_add(dev);

}

device_initialize

做一些初始化工作,dev->kobj.kset = devices_kset ; // 代表/sys/device目录

device_add

//设置dev->kobj.parent,即确定这个dev的父目录,详情见下节

setup_parent(dev, dev->parent);

// dev挂到dev->kobj.parent代表的目录如果没有parent父目录默认被设置成dev->kobj.kset代表的目录

       kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev->bus_id);

       device_create_file(dev, &uevent_attr);  // 给设备添加uevent属性

if (MAJOR(dev->devt))

device_create_file(dev, &devt_attr);     // 给设备添加dev属性(打印主从设备号)

device_create_sys_dev_entry(dev);      // 在设备的class下创建设备链接,比如/sys/char/sys/block,链接名字为major:minor;如果设备没有class,默认为/sys/char目录

device_add_class_symlinks(dev);        

// 在设备目录下建立subsystem链接,指向其所属的class

sysfs_create_link(&dev->kobj, &dev->class->p->class_subsys.kobj, "subsystem");

                     // 在设备所属class目录下建立指向设备的链接,以设备名命名

sysfs_create_link(&dev->class->p->class_subsys.kobj, &dev->kobj, dev->bus_id);

// 如果父设备存在,在设备目录下建立指向父设备的链接,以device命名

                     sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device");

              device_add_attrs(dev);

                     // 如果有class,把classdev_attrs属性都加上

                     device_add_attributes(dev, class->dev_attrs);

                     // 如果有type,把typedev_attrs属性都加上

                     device_add_groups(dev, type->groups);

                     // devicegroups指向的属性都加上

                     device_add_groups(dev, dev->groups);

              bus_add_device(dev);    // 在设备有bus时有效

                     // 如果有bus,将busdev_attrs都加上

                     device_add_attrs(bus_get(dev->bus), dev);

                     // bus中建立指向设备的链接,以设备名命名

sysfs_create_link(&bus->p->devices_kset->kobj, &dev->kobj, dev->bus_id);

// 在设备中建立指向总线的链接,以”subsystem”命名

sysfs_create_link(&dev->kobj, &dev->bus->p->subsys.kobj, "subsystem");

              dpm_sysfs_add(dev);    // 建立power属性

device_pm_add(dev);

kobject_uevent(&dev->kobj, KOBJ_ADD);

bus_attach_device(dev);

/* 如果总线支持自动检测设备(drivers_autoprobe == 1; 默认都支持),调用device_attach(dev); device_attach中,如果发现dev已经有driver与之关联,作一些sysfs的操作;如果没有,对总线中每一个驱动调用__device_attach

__device_attachàdriver_probe_device

driver_probe_device先调用busmatch函数,如果返回的是match,再调用really_probe。(busmatch函数在这里调用)

really_probe先看bus有没有probe函数,如果有,调用busprobe。如果没有,调用driverprobe函数(这里就是我们驱动程序的probe函数被调用的地方)。*/

if (bus->p->drivers_autoprobe)

       ret = device_attach(dev);

klist_add_tail(&dev->knode_parent, &parent->klist_children);

list_add_tail(&dev->node, &dev->class->p->class_devices);

list_for_each_entry(class_intf, &dev->class->p->class_interfaces, node)

       if (class_intf->add_dev)

              class_intf->add_dev(dev, class_intf);

 

3.3 device的四种类型

sysfs的设备模型中,有四种设备类型:

物理设备              parent设备,没有class

直接虚拟设备       parent设备和classparent没有class

间接虚拟设备       parent设备和classparentclass

纯虚拟设备           没有parent,有class(网络环回设备等)

 

以挂在PCI总线上的I2C适配器为例,首先需要创建一个设备,使其bus域指向PCI bus,这是一个物理设备;然后,以这个物理设备为父设备,创建一个classI2C_adapter_class的子设备,这个设备是直接虚拟设备,描述I2C adapter的功能。I2C子系统对每一个I2C adapter,又进一步创建了一个字符设备,I2C dev,这个字符设备的class被设置为I2C_device_class,这里I2C dev就是一个间接虚拟设备。

除非是纯虚设备,否则任何一个虚拟设备向父设备追溯,一定能找到一个物理设备。

 

struct device中有两个域,busclass,这两个域不能同时有值。如果bus域非空,说明这个struct device是挂在某个总线上,那么它必须是一个物理设备,class域必须是NULL;如果class域非空,说明这是一个属于某个类的虚拟设备,那么它的bus域就必须是NULL。所以,在上节提到的两个函数,device_add_class_symlinks(dev) bus_add_device(dev)中,虽然都创建了subsystem链接,但它们只有一个会起作用,否则系统会崩溃。

 

setup_parent函数

void setup_parent(struct device *dev, struct device *parent) ;

这个函数用来决定dev被加到sysfs的哪个目录下。代码逻辑为:

       kobj = get_device_parent(dev, parent);

       if (kobj)

              dev->kobj.parent = kobj;

 

static struct kobject *get_device_parent(struct device *dev, struct device *parent) ;

这个函数会按照设备类型决定设备的父目录 :

l         如果是物理设备且有父设备 dev->class == NULL && dev->parent

       父目录就是父设备代表的目录

l         如果是直接虚拟设备 dev->class && dev->parent && dev->parent->class != NULL

在父设备代表的目录下新建一个子目录,名字为dev->class->name。然后把这个新建的目录作为设备的父目录 :/sys/devices/<parent_name >/<class_name>

l         如果是间接虚拟设备 dev->class && dev->parent && dev->parent->class == NULL

       父目录就是父设备代表的目录

l         如果是纯虚拟设备 dev->class && dev->parent == NULL

       父目录为/sys/devices/virtual/<class_name>

l         如果是物理设备且没有父设备 dev->class == NULL && dev->parent == NULL

    本函数不设置父目录,返回NULL。但由于此函数返回后会继续调用kobject_add所以父目录会设置成dev->kobj->kset代表的目录也就是一开始device_initialize函数里设置的/sys/devices目录。可以看出这是物理root设备,比如platform /sys/devices/platform)。

 

 

4.   Driver

4.1 struct device_driver基本结构

struct device_driver {

         const char            *name;

         struct bus_type            *bus;

         struct module               *owner;

         const char                    *mod_name;        /* used for built-in modules */

         int (*probe) (struct device *dev);

         int (*remove) (struct device *dev);

         void (*shutdown) (struct device *dev);

         int (*suspend) (struct device *dev, pm_message_t state);

         int (*resume) (struct device *dev);

         struct attribute_group **groups;

         struct pm_ops *pm;

         struct driver_private *p;

};

 

struct driver_private {

         struct kobject kobj;

         struct klist klist_devices;

         struct klist_node knode_bus;

         struct module_kobject *mkobj;

         struct device_driver *driver;

};

4.2 driver_register

driver_registerdriver注册到sysfs系统中,在注册之前需要对driver进行初始化

struct device_driver driver = {

       .name = <driver name>;

       .bus = <bus>;

       .probe = <probe func>;                // 探测设备

       .remove = <remove func>;           // 移除设备

       .suspend = <suspend func>;         // 挂起设备 进入低功耗状态)

       .resume = <resume func>;           // 运行设备(从低功耗状态恢复)

};

 

int driver_register(struct device_driver *drv)              // driver登记到sysfs系统中

       bus_add_driver(drv);

       driver_add_groups(drv, drv->groups);

 

int bus_add_driver(struct device_driver *drv)

       // 分配并初始化struct driver_private

       。。。

       priv->kobj.kset = bus->p->drivers_kset;      // 父目录指向busdrivers子目录

       // driver新建一个kobj,父目录在上一行的kset中指定了

       kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, “s%”, drv->name);

       /* 如果drvbus支持autoprobebus->p->drivers_autoprobe==1;默认都是1),调用driver_attachdriver_attachbus中每一个device,调用__driver_attach__driver_attach调用driver_probe_device driver_probe_device的过程在上一章中的device_register函数中有描述。*/

       if (drv->bus->p->drivers_autoprobe)

              driver_attach(drv);

// driver加入到busdrivers列表

       klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);             

module_add_driver (drv->owner, drv);

driver_create_file(drv, &driver_attr_uevent);              // 加入uevent属性

driver_add_attrs(bus, drv);    // busdrv_attrs属性列表加入driver目录

add_bind_files(drv);              // 加入bindunbind属性(与热插拔有关)

kobject_uevent(&priv->kobj, KOBJ_ADD);

 

int driver_add_groups(struct device_driver *drv, struct attribute_group **groups)

group中每一个group,调用sysfs_create_groupdriver下创建一个子目录,并将group里的属性作为文件加入到子目录中。

      

5.   sysfs

前面提到的classbusdriversdevicessysfs系统中都是以目录表示;它们的属性则由文件表示。所有的目录、文件都是通过sysfs模块提供的函数创建和维护。这些sysfs的函数主要包括:

 

// kobj代表的目录下新建一个文件

int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)

sysfs_add_file

       sysfs_add_file_mode

 

// kobj代表的目录下创建链接,指向target,链接名为name

int sysfs_create_link(struct kobject *kobj, struct kobject *target, const char *name);

 

// 如果grp->name存在,在kobj代表的子目录下创建以grp->name命名的子目录

// kobj代表的目录或新建的子目录下添加属性文件

int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)

 

// sysfs中创建子目录

struct kset *kset_create_and_add(const char *name, struct kset_uevent_ops *uevent_ops,

struct kobject *parent_kobj)