EDN首页   博客首页

1

关于投票
介绍linux驱动程序中ioctl的概念、意义和用法

     我这里说的ioctl函数是在驱动程序里的,因为我不知道还有没有别的场合用到了ioctl,
      所以就规定了我们讨论的范围。为什么要写篇文章呢,是因为我前一阵子被ioctl给搞混
      了,这几天才弄明白它,于是在这里清理一下头脑。 
      
      一、 什么是ioctl。 
      ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就
      是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用方法
      如下: 
      int ioctl(int fd, int cmd, …); 
      其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设
      备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和
      cmd的意义相关的。 
      ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支
      持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。 
      
      二、 ioctl的必要性 
      如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那就是蛮拧了。例如,我们可
      以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,
      那么后面就跟着控制命令(一般在socket编程中常常这样做)。但是如果这样做的话,会
      导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。 
      所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码告
      诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要
      做的事情。 
      
      三、 ioctl如何实现 
      这是一个很麻烦的问题,我是能省则省。要说清楚它,没有四五千字是不行的,所以我这
      里是不可能把它说得非常清楚了,不过如果有读者对用户程序怎么和驱动程序联系起来感
      兴趣的话,可以看我前一阵子写的《write的奥秘》。读者只要把write换成ioctl,就知
      道用户程序的ioctl是怎么和驱动程序中的ioctl实现联系在一起的了。 
      我这里说一个大概思路,因为我觉得《Linux设备驱动程序》这本书已经说的非常清楚
      了,但是得化一些时间来看。 
      在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对
      应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事
      情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在ioctl中
      命令码是唯一联系用户程序命令和驱动程序支持的途径。 
      命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不
      会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的
      命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇
      怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。 
      所以在Linux核心中是这样定义一个命令码的: 
      ____________________________________
      | 设备类型 | 序列号 | 方向 |数据尺寸|
      |----------|--------|------|--------|
      | 8 bit    |  8 bit |2 bit |8~14 bit|
      |----------|--------|------|--------|

      这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以
      Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从
      命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数
      据传送方向和数据传输尺寸。
       
      这些宏我就不在这里解释了,具体的形式请读者察看Linux核心源代码中的和,文件里给
      除了这些宏完整的定义。这里我只多说一个地方,那就是"幻数"。 
      幻数是一个字母,数据长度也是8,所以就用一个特定的字母来标明设备类型,这和用一
      个数字是一样的,只是更加利于记忆和理解。就是这样,再没有更复杂的了。 
      更多的说了也没有,读者还是看一看源代码吧,推荐各位阅读《Linux 设备驱动程序》所
      带源代码中的short一例,因为它比较短小,功能比较简单,可以看明白ioctl的功能和细
      节。 

      四、 cmd参数如何得出 
      这里确实要说一说,cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、
      数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解
      码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过
      switch{case}结构进行相应的操作。 
      要透彻理解,只能是通过阅读源代码,我这篇文章实际上只是一个引子。Cmd参数的组织
      还是比较复杂的,我认为要搞熟它还是得花不少时间的,但是这是值得的,驱动程序中最
      难的是对中断的理解。 

      五、 小结 
      ioctl其实没有什么很难的东西需要理解,关键是理解cmd命令码是怎么在用户程序里生成
      并在驱动程序里解析的,程序员最主要的工作量在switch{case}结构中,因为对设备的
      I/O控制都是通过这一部分的代码实现的。 

      参考资料: 
      1.《Linux 设备驱动程序》,鲁宾尼著,中国电力出版社。 
      2.《write的奥秘》,coly,真情流露(202.204.7.235)->电脑技术->Linux技术。

系统分类: 软件开发
用户分类: LINUX
标签: 无标签
来源: 整理
发表评论 阅读全文(490) | 回复(0)

1

关于投票
什么是结构体?


  简单的来说,结构体就是一个可以包含不同数据类型的一个结构,它是一种可以自己定义的数据类型,它的特点和数组主要有两点不同,首先结构体可以在一个结构中声明不同的数据类型,第二相同结构的结构体变量是可以相互赋值的,而数组是做不到的,因为数组是单一数据类型的数据集合,它本身不是数据类型(而结构体是),数组名称是常量指针,所以不可以做为左值进行运算,所以数组之间就不能通过数组名称相互复制了,即使数据类型和数组大小完全相同。

  定义结构体使用struct修饰符,例如:

struct test
{
     float a

     int b

};

  上面的代码就定义了一个名为test的结构体,它的数据类型就是test,它包含两个成员ab,成员a的数据类型为浮点型,成员b的数据类型为整型。

  由于结构体本身就是自定义的数据类型,定义结构体变量的方法和定义普通变量的方法一样。

test pn1;

  这样就定义了一test结构体数据类型的结构体变量pn1,结构体成员的访问通过点操作符进行,pn1.a=10 就对结构体变量pn1的成员a进行了赋值操作。

  注意:结构体声明的时候本身不占用任何内存空间,只有当你用你定义的结构体类型定义结构体变量的时候计算机才会分配内存。

  结构体,同样是可以定义指针的,那么结构体指针就叫做结构指针。

  结构指针通过->符号来访问成员,下面我们就以上所说的看一个完整的例子:

#include <iostream>   
#include <string>   
using namespace std; 
 
struct test//
定义一个名为test的结构体 

    int a;//
定义结构体成员
    int b;//
定义结构体成员
}; 
 
void main()     

    test pn1;//
定义结构体变量pn1 
    test pn2;//
定义结构体变量pn2 
 
    pn2.a=10;//
通过成员操作符.给结构体变量pn2中的成员a赋值 
    pn2.b=3;//
通过成员操作符.给结构体变量pn2中的成员b赋值 
 
    pn1=pn2;//
pn2中所有的成员值复制给具有相同结构的结构体变量pn1 
    cout<<pn1.a<<"|"<<pn1.b<<endl; 
    cout<<pn2.a<<"|"<<pn2.b<<endl; 
 
    test *point;//
定义结构指针 
 
    point=&pn2;//
指针指向结构体变量pn2的内存地址 
    cout<<pn2.a<<"|"<<pn2.b<<endl; 
    point->a=99;//
通过结构指针修改结构体变量pn2成员a的值 
    cout<<pn2.a<<"|"<<pn2.b<<endl; 
    cout<<point->a<<"|"<<point->b<<endl; 
    cin.get(); 
}

  总之,结构体可以描述数组不能够清晰描述的结构,它具有数组所不具备的一些功能特性。

 下面我们来看一下,结构体变量是如何作为函数参数进行传递的。

#include <iostream>   
#include <string>   
using namespace std; 
 
struct test 

    char name[10]; 
    float socre; 
}; 
 
void print_score(test pn)//
以结构变量进行传递 

    cout<<pn.name<<"|"<<pn.socre<<endl; 

 
void print_score(test *pn)//
一结构指针作为形参 

    cout<<pn->name<<"|"<<pn->socre<<endl; 

 
void main()     

    test a[2]=,}; 
    int num = sizeof(a)/sizeof(test); 
    for(int i=0;i<num;i++) 
    { 
        print_score(a[i]); 
    } 
    for(int i=0;i<num;i++) 
    { 
        print_score(&a[i]); 
    } 
    cin.get(); 
}

  void print_score(test *pn)的效率是要高过void print_score(test pn)的,因为直接内存操作避免了栈空间开辟结构变量空间需求,节省内存。

  下面我们再说一下,传递结构引用的例子。

  利用引用传递的好处很多,它的效率和指针相差无几,但引用的操作方式和值传递几乎一样,种种优势都说明善用引用可以做到程序的易读和易操作,它的优势尤其在结构和大的时候,避免传递结构变量很大的值,节省内存,提高效率。

 

 

#include <iostream>   
#include <string>   
using namespace std; 
 
struct test 

    char name[10]; 
    float socre; 
}; 
 
void print_score(test &pn)//
以结构变量进行传递 

    cout<<pn.name<<"|"<<pn.socre<<endl; 

 
void main()     

    test a[2]=,}; 
    int num = sizeof(a)/sizeof(test); 
    for(int i=0;i<num;i++) 
    { 
        print_score(a[i]); 
    } 
    cin.get(); 
}
 上面我们说明了易用引用对结构体进行操作的优势,下面我们重点对比两个例程,进一部分析关于效率的问题。

//-------------------------------------例程1--------------------------------- 
 
#include <iostream>   
#include <string>   
using namespace std; 
 
struct test 

    char name[10]; 
    float socre; 
}; 
 
void print_score(test &pn) 

    cout<<pn.name<<"|"<<pn.socre<<endl; 

 
test get_score() 

    test pn; 
    cin>>pn.name>>pn.socre; 
    return pn; 

void main() 

    test a[2]; 
    int num = sizeof(a)/sizeof(test); 
    for(int i=0;i<num;i++) 
    { 
        a[i]=get_score(); 
    } 
    cin.get(); 
    for(int i=0;i<num;i++) 
    { 
        print_score(a[i]); 
    } 
    cin.get(); 

 
//-------------------------------------
例程2---------------------------------  
  
#include <iostream>   
#include <string>   
using namespace std; 
 
struct test 

    char name[10]; 
    float socre; 
}; 
 
void print_score(test &pn) 

    cout<<pn.name<<"|"<<pn.socre<<endl; 

 
void get_score(test &pn) 

    cin>>pn.name>>pn.socre; 

void main() 

    test a[2]; 
    int num = sizeof(a)/sizeof(test); 
    for(int i=0;i<num;i++) 
    { 
        get_score(a[i]); 
    } 
    cin.get(); 
    for(int i=0;i<num;i++) 
    { 
        print_score(a[i]); 
    } 
    cin.get(); 
}

  例程2的效率要远高过例程1的原因主要有以下两处:


  第一:

  例程1中的

test get_score()
{
test pn;
cin>>pn.name>>pn.socre;
return pn;
}

  调用的时候在内部要在栈空间开辟一个名为pn的结构体变量,程序pn的时候又再次在栈内存空间内自动生成了一个临时结构体变量temp,在前面的教程中我们已经说过,它是一个copy,而例程2中的:

void get_score(test &pn)
{
cin>>pn.name>>pn.socre;
}

  却没有这一过程,不开辟任何新的内存空间,也没有任何临时变量的生成。

  第二:

  例程1mian()中,必须对返回的结构体变量进行一次结构体变量与结构体变量直接的相互赋值操作。

for(int i="0";i<num;i++)
{
a[i]=get_score();
}

  而例程2中由于是通过内存地址直接操作,所以完全没有这一过程,提高了效率。

for(int i="0";i<num;i++)
{
get_score(a[i]);
}

  函数也是可以返回结构体应用的,例子如下:

#include <iostream>   
#include <string>   
using namespace std; 
 
struct test 

    char name[10]; 
    float socre; 
}; 
 
test a; 
 
 
test &get_score(test &pn) 

    cin>>pn.name>>pn.socre; 
    return pn; 

 
void print_score(test &pn)   
{   
    cout<<pn.name<<"|"<<pn.socre<<endl;   
}   
 
void main() 

    test &sp=get_score(a); 
    cin.get(); 
    cout<<sp.name<<"|"<<sp.socre; 
    cin.get();  
}

  调用get_score(a);结束并返回的时候,函数内部没有临时变量的产生,返回直接吧全局结构变量a的内存地址赋予结构引用sp

  最后提一下指针的引用

  定义指针的引用方法如下:

void main() 

int a=0; 
int b=10; 
int *p1=&a; 
int *p2=&b; 
int *&pn=p1; 
cout <<pn<<"|"<<*pn<<endl; 
pn=p2; 
cout <<pn<<"|"<<*pn<<endl; 
cin.get(); 
}

  pn就是一个指向指针的引用,它也可以看做是指针别名,总之使用引用要特别注意它的特性,它的操作是和普通指针一样的,在函数中对全局指针的引用操作要十分小心,避免破坏全局指针!

 

系统分类: 软件开发
用户分类: 知识库
标签: 结构体
来源: 整理
发表评论 阅读全文(137) | 回复(0)
总共 , 当前 /