轉載:Linux核心的ioctl函式
關於ioctl的網上資源
一、 什麼是ioctl。
ioctl是裝置驅動程式中對裝置的I/O通道進行管理的函式。所謂對I/O通道進行管理,就是對裝置的一些特性進行控制,例如串列埠的傳輸波特率、馬達的轉速等等。它的呼叫個數如下:
int ioctl(int fd, ind cmd, …);
其中fd就是使用者程式開啟裝置時使用open函式返回的檔案標示符,cmd就是使用者程式對裝置的控制命令,至於後面的省略號,那是一些補充引數,一般最多一個,有或沒有是和cmd的意義相關的。
ioctl函式是檔案結構中的一個屬性分量,就是說如果你的驅動程式提供了對ioctl的支援,使用者就可以在使用者程式中使用ioctl函式控制裝置的I/O通道。
二、 ioctl的必要性
如果不用ioctl的話,也可以實現對裝置I/O通道的控制,但那就是蠻擰了。例如,我們可以在驅動程式中實現write的時候檢查一下是否有特殊約定的資料流通過,如果有的話,那麼後面就跟著控制命令(一般在socket程式設計中常常這樣做)。但是如果這樣做的話,會導致程式碼分工不明,程式結構混亂,程式設計師自己也會頭昏眼花的。
所以,我們就使用ioctl來實現控制的功能。要記住,使用者程式所作的只是通過命令碼告訴驅動程式它想做什麼,至於怎麼解釋這些命令和怎麼實現這些命令,這都是驅動程式要做的事情。
三、 ioctl如何實現
在驅動程式中實現的ioctl函式體內,實際上是有一個switch{case}結構,每一個case對應一個命令碼,做出一些相應的操作。怎麼實現這些操作,這是每一個程式設計師自己的事情,因為裝置都是特定的,這裡也沒法說。關鍵在於怎麼樣組織命令碼,因為在ioctl中命令碼是唯一聯絡使用者程式命令和驅動程式支援的途徑。
命令碼的組織是有一些講究的,因為我們一定要做到命令和裝置是一一對應的,這樣才不會將正確的命令發給錯誤的裝置,或者是把錯誤的命令發給正確的裝置,或者是把錯誤的命令發給錯誤的裝置。這些錯誤都會導致不可預料的事情發生,而當程式設計師發現了這些奇怪的事情的時候,再來除錯程式查詢錯誤,那將是非常困難的事情。
所以在Linux核心中是這樣定義一個命令碼的:
____________________________________
| 裝置型別 | 序列號 | 方向 |資料尺寸|
|----------|--------|------|--------|
| 8 bit | 8 bit |2 bit |8~14 bit|
|----------|--------|------|--------|
這樣一來,一個命令就變成了一個整數形式的命令碼。但是命令碼非常的不直觀,所以Linux Kernel中提供了一些巨集,這些巨集可根據便於理解的字串生成命令碼,或者是從命令碼得到一些使用者可以理解的字串以標明這個命令對應的裝置型別、裝置序列號、資料傳送方向和資料傳輸尺寸。
幻數是一個字母,資料長度也是8,所以就用一個特定的字母來標明裝置型別,這和用一個數字是一樣的,只是更加利於記憶和理解。就是這樣,再沒有更復雜的了。
四、 cmd引數如何得出
cmd引數在使用者程式端由一些巨集根據裝置型別、序列號、傳送方向、資料尺寸等生成,這個整數通過系統呼叫傳遞到核心中的驅動程式,再由驅動程式使用解碼巨集從這個整數中得到裝置的型別、序列號、傳送方向、資料尺寸等資訊,然後通過switch{case}結構進行相應的操作。
五、 小結
ioctl其實沒有什麼很難的東西需要理解,關鍵是理解cmd命令碼是怎麼在使用者程式裡生成並在驅動程式裡解析的,程式設計師最主要的工作量在switch{case}結構中,因為對裝置的I/O控制都是通過這一部分的程式碼實現的。
六、結合自己的程式碼例子,以PCM讀寫函式在android和kernel之間傳遞為例子
先看android的程式碼
- int pcm_write(struct pcm *pcm, void *data, unsigned count)
- {
- struct snd_xferi x;
- if (pcm->flags & PCM_IN)
- return -EINVAL;
- x.buf = data;
- x.frames = (pcm->flags & PCM_MONO) ? (count / 2) : (count / 4);
- for (;;) {
- if (!pcm->running) {
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE))
- return oops(pcm, errno, "cannot prepare channel");
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
- return oops(pcm, errno, "cannot write initial data");
- pcm->running = 1;
- return 0;
- }
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
- pcm->running = 0;
- if (errno == EPIPE) {
- /* we failed to make our window -- try to restart */
- pcm->underruns++;
- continue;
- }
- return oops(pcm, errno, "cannot write stream data");
- }
- return 0;
- }
- }
- #define SNDRV_PCM_IOCTL_WRITEI_FRAMES _IOW('A', 0x50, struct snd_xferi)
- #define SNDRV_PCM_IOCTL_READI_FRAMES _IOR('A', 0x51, struct snd_xferi)
- #define SNDRV_PCM_IOCTL_WRITEN_FRAMES _IOW('A', 0x52, struct snd_xfern)
- #define SNDRV_PCM_IOCTL_READN_FRAMES _IOR('A', 0x53, struct snd_xfern)
kernel的程式碼
- #define SNDRV_PCM_IOCTL_WRITEI_FRAMES _IOW('A', 0x50, struct snd_xferi)
- #define SNDRV_PCM_IOCTL_READI_FRAMES _IOR('A', 0x51, struct snd_xferi)
- #define SNDRV_PCM_IOCTL_WRITEN_FRAMES _IOW('A', 0x52, struct snd_xfern)
- #define SNDRV_PCM_IOCTL_READN_FRAMES _IOR('A', 0x53, struct snd_xfern)
- static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
- {
- struct snd_pcm_file *pcm_file;
- pcm_file = file->private_data;
- if (((cmd >> 8) & 0xff) != 'A')
- return -ENOTTY;
- return snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd,
- (void __user *)arg);
- }
- static int snd_pcm_playback_ioctl1(struct file *file,
- struct snd_pcm_substream *substream,
- unsigned int cmd, void __user *arg)
- {
- if (snd_BUG_ON(!substream))
- return -ENXIO;
- if (snd_BUG_ON(substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
- return -EINVAL;
- switch (cmd) {
- case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
- {
- struct snd_xferi xferi;
- struct snd_xferi __user *_xferi = arg;
- struct snd_pcm_runtime *runtime = substream->runtime;
- snd_pcm_sframes_t result;
- if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
- return -EBADFD;
- if (put_user(0, &_xferi->result))
- return -EFAULT;
- if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
- return -EFAULT;
- result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
- __put_user(result, &_xferi->result);
- return result < 0 ? result : 0;
- }
- case SNDRV_PCM_IOCTL_WRITEN_FRAMES:
- {
- struct snd_xfern xfern;
- struct snd_xfern __user *_xfern = arg;
- struct snd_pcm_runtime *runtime = substream->runtime;
- void __user **bufs;
- snd_pcm_sframes_t result;
- if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
- return -EBADFD;
- if (runtime->channels > 128)
- return -EINVAL;
- if (put_user(0, &_xfern->result))
- return -EFAULT;
- if (copy_from_user(&xfern, _xfern, sizeof(xfern)))
- return -EFAULT;
- bufs = memdup_user(xfern.bufs,
- sizeof(void *) * runtime->channels);
- if (IS_ERR(bufs))
- return PTR_ERR(bufs);
- result = snd_pcm_lib_writev(substream, bufs, xfern.frames);
- kfree(bufs);
- __put_user(result, &_xfern->result);
- return result < 0 ? result : 0;
- }
- case SNDRV_PCM_IOCTL_REWIND:
- {
- snd_pcm_uframes_t frames;
- snd_pcm_uframes_t __user *_frames = arg;
- snd_pcm_sframes_t result;
- if (get_user(frames, _frames))
- return -EFAULT;
- if (put_user(0, _frames))
- return -EFAULT;
- result = snd_pcm_playback_rewind(substream, frames);
- __put_user(result, _frames);
- return result < 0 ? result : 0;
- }
- case SNDRV_PCM_IOCTL_FORWARD:
- {
- snd_pcm_uframes_t frames;
- snd_pcm_uframes_t __user *_frames = arg;
- snd_pcm_sframes_t result;
- if (get_user(frames, _frames))
- return -EFAULT;
- if (put_user(0, _frames))
- return -EFAULT;
- result = snd_pcm_playback_forward(substream, frames);
- __put_user(result, _frames);
- return result < 0 ? result : 0;
- }
- }
- return snd_pcm_common_ioctl1(file, substream, cmd, arg);
- }
android 和 kernel 兩邊各自的巨集定義是一樣的,通過這些用32bit資料代表的資料傳遞,實現了從android到kernel的介面連線