ioctl函數的作用

ioctl函數的作用

特殊的read,write, 當你用read,write不能完成某一功能時,就用ioctl

我這裏說的ioctl函數是在驅動程序裏的,因爲我不知道還有沒有別的場合用到了ioctl,所以就規定了我們討論的範圍。爲什麼要寫篇文章呢,是因爲我前一陣子被ioctl給搞混了,這幾天才弄明白它,於是在這裏清理一下頭腦。

什麼是ioctl。

ioctl是設備驅動程序中對設備的I/O通道進行管理的函數。所謂對I/O通道進行管理,就是對設備的一些特性進行控制,例如串口的傳輸波特率、馬達的轉速等等。它的調用個數如下:
int ioctl(int fd, ind cmd, …);
其中fd就是用戶程序打開設備時使用open函數返回的文件標示符,cmd就是用戶程序對設備的控制命令,至於後面的省略號,那是一些補充參數,一般最多一個,有或沒有是和cmd的意義相關的。
ioctl函數是文件結構中的一個屬性分量,就是說如果你的驅動程序提供了對ioctl的支持,用戶就可以在用戶程序中使用ioctl函數控制設備的I/O通道。 【所以說這些cmd不能和內核中的相同,否則就有衝突了】

ioctl的必要性

如果不用ioctl的話,也可以實現對設備I/O通道的控制,但那就是蠻擰了。例如,我們可以在驅動程序中實現write的時候檢查一下是否有特殊約定的數據流通過,如果有的話,那麼後面就跟着控制命令(一般在socket編程中常常這樣做)。但是如果這樣做的話,會導致代碼分工不明,程序結構混亂,程序員自己也會頭昏眼花的。所以,我們就使用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控制都是通過這一部分的代碼實現的。

ioctl 接口

大部分驅動需要 – 除了讀寫設備的能力 – 通過設備驅動進行各種硬件控制的能力. 大部分設備可進行超出簡單的數據傳輸之外的操作; 用戶空間必須常常能夠請求, 例如, 設備鎖上它的門, 彈出它的介質, 報告錯誤信息, 改變波特率, 或者自我銷燬. 這些操作常常通過 ioctl 方法來支持, 它通過相同名子的系統調用來實現.

在用戶空間, ioctl 系統調用有下面的原型:
int ioctl(int fd, unsigned long cmd, …);

這個原型由於這些點而凸現於 Unix 系統調用列表, 這些點常常表示函數有數目不定的參數. 在實際系統中, 但是, 一個系統調用不能真正有變數目的參數. 系統調用必須有一個很好定義的原型, 因爲用戶程序可存取它們只能通過硬件的”門”. 因此, 原型中的點不表示一個變數目的參數, 而是一個單個可選的參數, 傳統上標識爲 char *argp. 這些點在那裏只是爲了阻止在編譯時的類型檢查. 第 3 個參數的實際特點依賴所發出的特定的控制命令( 第 2 個參數 ). 一些命令不用參數, 一些用一個整數值, 以及一些使用指向其他數據的指針. 使用一個指針是傳遞任意數據到 ioctl 調用的方法; 設備接着可與用戶空間交換任何數量的數據.

ioctl 調用的非結構化特性使它在內核開發者中失寵. 每個 ioctl 命令, 基本上, 是一個單獨的, 常常無文檔的系統調用, 並且沒有方法以任何類型的全面的方式覈查這些調用. 也難於使非結構化的 ioctl 參數在所有系統上一致工作; 例如, 考慮運行在 32-位模式的一個用戶進程的 64-位 系統. 結果, 有很大的壓力來實現混雜的控制操作, 只通過任何其他的方法. 可能的選擇包括嵌入命令到數據流(本章稍後我們將討論這個方法)或者使用虛擬文件系統, 要麼是 sysfs 要麼是設備特定的文件系統. (我們將在 14 章看看 sysfs). 但是, 事實是 ioctl 常常是最容易的和最直接的選擇,對於真正的設備操作.

ioctl 驅動方法有和用戶空間版本不同的原型:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

inode 和 filp 指針是對應應用程序傳遞的文件描述符 fd 的值, 和傳遞給 open 方法的相同參數. cmd 參數從用戶那裏不改變地傳下來, 並且可選的參數 arg 參數以一個 unsigned long 的形式傳遞, 不管它是否由用戶給定爲一個整數或一個指針. 如果調用程序不傳遞第 3 個參數, 被驅動操作收到的 arg 值是無定義的. 因爲類型檢查在這個額外參數上被關閉, 編譯器不能警告你如果一個無效的參數被傳遞給 ioctl, 並且任何關聯的錯誤將難以查找.

如果你可能想到的, 大部分 ioctl 實現包括一個大的 switch 語句來根據 cmd 參數, 選擇正確的做法. 不同的命令有不同的數值, 它們常常被給予符號名來簡化編碼. 符號名通過一個預處理定義來安排. 定製的驅動常常聲明這樣的符號在它們的頭文件中; scull.h 爲 scull 聲明它們. 用戶程序必須, 當然, 包含那個頭文件來存取這些符號.

  1. 選擇 ioctl 命令
    在爲 ioctl 編寫代碼之前, 你需要選擇對應命令的數字. 許多程序員的第一個本能的反應是選擇一組小數從0或1開始, 並且從此開始向上. 但是, 有充分的理由不這樣做. ioctl 命令數字應當在這個系統是唯一的, 爲了阻止向錯誤的設備發出正確的命令而引起的錯誤. 這樣的不匹配不會不可能發生, 並且一個程序可能發現它自己試圖改變一個非串口輸入系統的波特率, 例如一個 FIFO 或者一個音頻設備. 如果這樣的 ioctl 號是唯一的, 這個應用程序得到一個 EINVAL 錯誤而不是繼續做不應當做的事情.

爲幫助程序員創建唯一的 ioctl 命令代碼, 這些編碼已被劃分爲幾個位段. Linux 的第一個版本使用 16-位數: 高 8 位是關聯這個設備的”魔”數, 低 8 位是一個順序號, 在設備內唯一. 這樣做是因爲 Linus 是”無能”的(他自己的話); 一個更好的位段劃分僅在後來被設想. 不幸的是, 許多驅動仍然使用老傳統. 它們不得不: 改變命令編碼會破壞大量的二進制程序,並且這不是內核開發者願意見到的.

根據 Linux 內核慣例來爲你的驅動選擇 ioctl 號, 你應當首先檢查 include/asm/ioctl.h 和 Documentation/ioctl-number.txt. 這個頭文件定義你將使用的位段: type(魔數), 序號, 傳輸方向, 和參數大小. ioctl-number.txt 文件列舉了在內核中使用的魔數,[20] 因此你將可選擇你自己的魔數並且避免交疊. 這個文本文件也列舉了爲什麼應當使用慣例的原因.

定義 ioctl 命令號的正確方法使用 4 個位段, 它們有下列的含義. 這個列表中介紹的新符號定義在

#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)

#define SCULL_IOC_MAXNR 14

真正的源文件定義幾個額外的這裏沒有出現的命令.

我們選擇實現 2 種方法傳遞整數參數: 通過指針和通過明確的值(儘管, 由於一個已存在的慣例, ioclt 應當通過指針交換值). 類似地, 2 種方法被用來返回一個整數值:通過指針和通過設置返回值. 這個有效只要返回值是一個正的整數; 如同你現在所知道的, 在從任何系統調用返回時, 一個正值被保留(如同我們在 read 和 write 中見到的), 而一個負值被看作一個錯誤並且被用來在用戶空間設置 errno.[21]

“exchange” 和”shift”操作對於 scull 沒有特別的用處. 我們實現”exchange”來顯示驅動如何結合獨立的操作到單個的原子的操作, 並且”shift”來連接”tell”和”query”. 有時需要象這樣的原子的測試-和-設置操作, 特別地, 當應用程序需要設置和釋放鎖.

命令的明確的序號沒有特別的含義. 它只用來區分命令. 實際上, 你甚至可使用相同的序號給一個讀命令和一個寫命令, 因爲實際的 ioctl 號在”方向”位是不同的, 但是你沒有理由這樣做. 我們選擇在任何地方不使用命令的序號除了聲明中, 因此我們不分配一個返回值給它. 這就是爲什麼明確的號出現在之前給定的定義中. 這個例子展示了一個使用命令號的方法, 但是你有自由不這樣做.

除了少數幾個預定義的命令(馬上就討論), ioctl 的 cmd 參數的值當前不被內核使用, 並且在將來也很不可能. 因此, 你可以, 如果你覺得懶, 避免前面展示的複雜的聲明並明確聲明一組調整數字. 另一方面, 如果你做了, 你不會從使用這些位段中獲益, 並且你會遇到困難如果你曾提交你的代碼來包含在主線內核中. 頭文件

int err = 0, tmp;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR)
return -ENOTTY;

/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;

在調用 access_ok 之後, 驅動可安全地進行真正的傳輸. 加上 copy_from_user 和 copy_to_user_ 函數, 程序員可利用一組爲被最多使用的數據大小(1, 2, 4, 和 8 字節)而優化過的函數. 這些函數在下面列表中描述, 它們定義在

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>

static void usage()
{
printf("usage : ipconfig interface \n");
exit(0);
}

int main(int argc,char **argv)
{
struct sockaddr_in *addr;
struct ifreq ifr;
char *name,*address;
int sockfd;

if(argc != 2)  usage();
else  name = argv[1];

sockfd = socket(AF_INET,SOCK_DGRAM,0);
strncpy(ifr.ifr_name,name,IFNAMSIZ-1);

if(ioctl(sockfd,SIOCGIFADDR,&ifr) == -1)
perror("ioctl error"),exit(1);

addr = (struct sockaddr_in *)&(ifr.ifr_addr);
address = inet_ntoa(addr->sin_addr);
printf("inet addr: %s ",address);

if(ioctl(sockfd,SIOCGIFBRDADDR,&ifr) == -1)
perror("ioctl error"),exit(1);

addr = (struct sockaddr_in *)&ifr.ifr_broadaddr;
address = inet_ntoa(addr->sin_addr);
printf("broad addr: %s ",address);

if(ioctl(sockfd,SIOCGIFNETMASK,&ifr) == -1)
perror("ioctl error"),exit(1);
addr = (struct sockaddr_in *)&ifr.ifr_addr;
address = inet_ntoa(addr->sin_addr);
printf("inet mask: %s ",address);

printf("\n");
exit(0);
}

********************** 程序2*****************************************************

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <stdlib.h>
#include <unistd.h>
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned char u8;
#include <linux/ethtool.h>
#include <linux/sockios.h>

int detect_mii(int skfd, char *ifname)
{
struct ifreq ifr;
u16 *data, mii_val;
unsigned phy_id;

/* Get the vitals from the interface. */
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);

if (ioctl(skfd, SIOCGMIIPHY, &ifr) < 0)
{
fprintf(stderr, "SIOCGMIIPHY on %s failed: %s\n", ifname, strerror(errno));
(void) close(skfd);
return 2;
}

data = (u16 *)(&ifr.ifr_data);
phy_id = data[0];
data[1] = 1;

if (ioctl(skfd, SIOCGMIIREG, &ifr) < 0)
{
fprintf(stderr, "SIOCGMIIREG on %s failed: %s\n", ifr.ifr_name, strerror(errno));
return 2;
}

mii_val = data[3];
return(((mii_val & 0x0016) == 0x0004) ? 0 : 1);
}

int detect_ethtool(int skfd, char *ifname)
{
struct ifreq ifr;
struct ethtool_value edata;
memset(&ifr, 0, sizeof(ifr));
edata.cmd = ETHTOOL_GLINK;

strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)-1);
ifr.ifr_data = (char *) &edata;

if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1)
{
printf("ETHTOOL_GLINK failed: %s\n", strerror(errno));
return 2;
}

return (edata.data ? 0 : 1);
}

int main(int argc, char **argv)
{
int skfd = -1;
char *ifname;
int retval;

if( argv[1] )  ifname = argv[1];
else  ifname = "eth0";

/* Open a socket. */
if (( skfd = socket( AF_INET, SOCK_DGRAM, 0 ) ) < 0 )
{
printf("socket error\n");
exit(-1);
}

retval = detect_ethtool(skfd, ifname);
if (retval == 2)
retval = detect_mii(skfd, ifname);

close(skfd);

if (retval == 2)
printf("Could not determine status\n");
if (retval == 1)
printf("Link down\n");
if (retval == 0)
printf("Link up\n");

return retval;
}

*********************程序3*****************************************************

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <net/if.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>

#define LINKTEST_GLINK 0x0000000a

struct linktest_value {
unsigned int    cmd;
unsigned int    data;
};

static void usage(const char * pname)
{
fprintf(stderr, "usage: %s <device>\n", pname);
fprintf(stderr, "returns: \n");
fprintf(stderr, "\t 0: link detected\n");
fprintf(stderr, "\t%d: %s\n", ENODEV, strerror(ENODEV));
fprintf(stderr, "\t%d: %s\n", ENONET, strerror(ENONET));
fprintf(stderr, "\t%d: %s\n", EOPNOTSUPP, strerror(EOPNOTSUPP));
exit(EXIT_FAILURE);
}

static int linktest(const char * devname)
{
struct ifreq ifr;
struct linktest_value edata;
int fd;

/* setup our control structures. */
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, devname);

/* open control socket. */
fd=socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0 )
return -ECOMM;

errno=0;
edata.cmd = LINKTEST_GLINK;
ifr.ifr_data = (caddr_t)&edata;

if(!ioctl(fd, SIOCETHTOOL, &ifr))
{
if(edata.data)
{
fprintf(stdout, "link detected on %s\n", devname);
return 0;
} else
{
errno=ENONET;
}
}

perror("linktest");
return errno;
}

int main(int argc, char *argv[])
{
if(argc != 2)
{
usage(argv[0]);
}
return linktest(argv[1]);
}

***************************程序4*********************************************************

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <stdio.h>
#include <unistd.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#define  BASE_VALUE 257

int main(int argc,char *argv[])
{
int mixer_fd=0;
char *names[SOUND_MIXER_NRDEVICES]=SOUND_DEVICE_LABELS;
int value,i;

printf("\nusage:%s dev_no.[0..24] value[0..100]\n\n",argv[0]);
printf("eg. %s 0 100\n",argv[0]);
printf("will change the volume to MAX volume.\n\n");
printf("The dev_no. are as below:\n");

for (i=0;i<SOUND_MIXER_NRDEVICES;i++)
{
if (i%3==0) printf("\n");
printf("%s:%d\t\t",names,i);
}

printf("\n\n");

if (argc<3)  exit(1);

if ((mixer_fd = open("/dev/mixer",O_RDWR)))
{
printf("Mixer opened successfully,working...\n");
value=BASE_VALUE*atoi(argv[2]);

if (ioctl(mixer_fd,MIXER_WRITE(atoi(argv[1])),&value)==0)
printf("successfully.....");
else
printf("unsuccessfully.....");

printf("done.\n");
}
else
printf("can't open /dev/mixer error....\n");

exit(0);
}

轉載自: https://blog.csdn.net/bamboolsu/article/details/43058751


聯繫方式: [email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章