liunx使用者空間和核心空間之間的通訊實現(在PPC下的實現)(轉)

ba發表於2007-08-15
liunx使用者空間和核心空間之間的通訊實現(在PPC下的實現)(轉)[@more@]系統呼叫
使用者空間和核心空間之間的通訊實現

● 與系統呼叫相關的資料結構和函式
系統呼叫函式名以“sys_”開頭,後面是該系統呼叫的名字,由此構成了221個形似sys_name()的函式名。
include/asm-i386/unistd.h中為每一個系統呼叫規定了惟一的編號,假設用name來表示系統呼叫的名稱,那麼系統呼叫號與系統呼叫響應函式的關係是:以系統呼叫號__NR_name作為下標,可找出系統呼叫表sys_call_table中對應表項的內容,它也就是該系統呼叫的響應函式sys_name的入口地址。

● 系統呼叫具體執行流程
當執行一個系統呼叫時,處理器跳轉到地址 0xc00
參考程式碼 arch/ppc/kernel/head.S
/* System call */
. = 0xc00
SystemCall:
EXCEPTION_PROLOG
/* EXCEPTION_PROLOG 是一個宏,負責從使用者空間到核心空間的切換,並需要儲存使用者程式的暫存器狀態*/
stw r3,ORIG_GPR3(r21)
li r20,MSR_KERNEL
rlwimi r20,r23,0,16,16 /* copy EE bit from saved MSR */
bl transfer_to_handler
.long DoSyscall
.long ret_from_except

有關DoSyscall,它在檔案arch/ppc/kernel/entry.S 中定義。這個函式最終使用系統呼叫編號將系統呼叫表的地址和索引載入,作業系統使用系統呼叫表將系統呼叫編號翻譯為特定的系統呼叫。
系統呼叫表名為 sys_call_table,在 arch/ppc/kernel/misc.S 中定義。系統呼叫表包含有實現每個系統呼叫的函式的地址。
………………………
_GLOBAL(sys_call_table)
.long sys_ni_syscall /* 0 old "setup()" system call */
………………………
long sys_getegid /* 50 */
.long sys_acct
.long sys_umount /* recycled never used phys() */
.long sys_ni_syscall /* old lock syscall holder */
.long sys_ioctl /* 54 */
.long sys_fcntl /* 55 */
………………………
當DoSyscall 找到正確的系統呼叫地址後,它將呼叫指定的系統呼叫函式。如要做系統ioctl呼叫,對應的系統呼叫號為54,它將呼叫函式sys_ioctl()。下面具體會說明sys_ioctl()的呼叫過程。
當函式呼叫完畢之後,返回到 DoSyscall(),它將控制權切換給 ret_from_except(在 arch/ppc/kernel/entry.S 中定義)。它會去檢查那些在切換回使用者空間之前需要完成的任務。如果沒有需要做的事情,那麼就透過 restore 函式恢復使用者程式的狀態,並將控制權交還給使用者程式。

● ioctl系統呼叫的整個流程
sys_ioctl()是整個ioctl系統呼叫過程中的最頂級函式,它需要對輸入的引數進行預處理,檢查引數的合法性,然後呼叫底層的處理函式作更進一步的處理。
分析函式sys_ioctl(),參考程式碼fs/ioctl.c
asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)
{
struct file * filp;
unsigned int flag;
int on, error = -EBADF;

filp = fget(fd);
/*透過傳入的引數檔案控制程式碼fd來獲得需要操作的檔案(或者裝置)的指標,後面做了具體的說明*/
if (!filp)
goto out;
error = 0;
TRACE_FILE_SYSTEM(TRACE_EV_FILE_SYSTEM_IOCTL,
fd,
cmd,
NULL);
lock_kernel();
switch (cmd) { /*不同的傳入命令字引數cmd的處理*/
case FIOCLEX:
set_close_on_exec(fd, 1);
break;

case FIONCLEX:
set_close_on_exec(fd, 0);
break;

case FIONBIO:
if ((error = get_user(on, (int *)arg)) != 0)
break;
flag = O_NONBLOCK;
#ifdef __sparc__
/* SunOS compatibility item. */
if(O_NONBLOCK != O_NDELAY)
flag |= O_NDELAY;
#endif
if (on)
filp->f_flags |= flag;
else
filp->f_flags &= ~flag;
break;

case FIOASYNC:
if ((error = get_user(on, (int *)arg)) != 0)
break;
flag = on ? FASYNC : 0;

/* Did FASYNC state change ? */
if ((flag ^ filp->f_flags) & FASYNC) {
if (filp->f_op && filp->f_op->fasync)
error = filp->f_op->fasync(fd, filp, on);
else error = -ENOTTY;
}
if (error != 0)
break;

if (on)
filp->f_flags |= FASYNC;
else
filp->f_flags &= ~FASYNC;
break;

default:
/*如果傳入的命令字引數cmd不符合上述情況,則需要呼叫更底層的ioctl處理函式
error = -ENOTTY;
/*下面根據情況呼叫ioctl處理函式*/
if (S_ISREG(filp->f_dentry->d_inode->i_mode))
error = file_ioctl(filp, cmd, arg); /*執行關於檔案的ioctl的一般操作*/
else if (filp->f_op && filp->f_op->ioctl)
/*如果filp本身是一個裝置,則執行filp->f_op->ioctl()函式,對裝置進行ioctl函式操作,該指標在初始化時就已經指向了裝置函式介面中的ioctl函式,因此在裝置初始化時,只要向核心提交了file_operations{}結構或block_device_operations{},其中的ioctl函式就會被呼叫到*/
error = filp->f_op->ioctl(filp->f_dentry->d_inode, filp, cmd, arg);
}
unlock_kernel();
fput(filp);

out:
return error;
}
其中呼叫到函式的說明:
★ fget()函式,它是用來獲取操作檔案的指標,在這篇文件裡,我是使用socket建立了一個檔案描述符,fd = socket(AF_INET6, SOCK_DGRAM, 0);
使用者態的建立socket()到核心中傳給函式sys_socket()處理, sys_socket()函式先呼叫函式sock_create()建立socket,然後把socket操作和檔案操作關聯起來,具體呼叫函式sock_map_fd()來實現,成功後將檔案描述和檔案結構file都儲存在sock->file中。
對於函式fget()函式,它首先呼叫fcheck函式,檢查一下檔案描述符fd是否對應一個開啟的檔案,如果是就獲取該檔案,呼叫函式get_file()將f_count加1。
具體看一下fcheck函式的執行,參考程式碼include/linux/file.h
static inline struct file * fcheck(unsigned int fd)
{
struct file * file = NULL;
struct files_struct *files = current->files;

if (fd < files->max_fds) /*max_fds是最多開啟的檔案數*/
file = files->fd[fd]; /*其中files_struct結構中定義的file是程式檔案描述符表*/
return file;
}
★ filp->f_op->ioctl()函式,呼叫裝置對應的ioctl函式,對於使用socket建立檔案描述符,它應該呼叫sock_ioctl()函式,具體流程圖如下:


每一個裝置都可以定義自己的ioctl命令字,命令編號的範圍是SIOCDEVPRIVATE到SIOCDEVPRIVATE + 15。針對ipv6隧道,它定一個四個命令字,分別是SIOCGETTUNNEL,SIOCADDTUNNEL,SIOCCHGTUNNEL,SIOCDELTUNNEL。使用者空間透過ioctl系統呼叫,最終呼叫到核心中定義的函式ip6ip6_tnl_ioctl。


sock_ioctl()函式呼叫中有關的核心函式:
sock_ioctl
inet6_ioctl
dev_ioctl
dev_ifsioc
■ sock_ioctl()
功能::直接呼叫一個協議特定的函式,如:當socket family是PF_INET6,呼叫函式inet6_ioctl。

■ inet6_ioctl()
功能:v6對應socket的ioctl核心函式,根據不同的case情況,作相應的處理。
。。。。。。。。。。。。。
case SIOCADDRT:
case SIOCDELRT:
return(ipv6_route_ioctl(cmd,(void *)arg));/*對路由表的ioctl操作,呼叫核心函式ipv6_route_ioctl進行增加或是刪除*/
。。。。。。。。。。。。。。
當ioctl命令字不滿足上述各種case情況時:
default:
if ((cmd >= SIOCDEVPRIVATE) &&
(cmd <= (SIOCDEVPRIVATE + 15)))
return(dev_ioctl(cmd,(void *) arg));
/*該裝置自己定義了一些ioctl命令字範圍在SIOCDEVPRIVATE到SIOCDEVPRIVATE + 15之間),呼叫函式dev_ioctl實現對該裝置指定的ioctl命令的操作*/

■ dev_ioctl()
功能:用來處理所有裝置介面的ioctl請求,只是一個包裝器, 實際的動作將由dev_ifsioc()來實現。dev_ioctl做的只是檢查這個呼叫是否具有了正當的許可權。
具體實現流程圖:



■ dev_ifsioc()
功能:真正處理所有裝置介面的ioctl請求。
具體操作說明:
函式首先要做的一些事情包括得到與ifr.ifr_name相匹配的裝置的結構,但這是在實現特定的介面命令之後。這些特定的介面命令被放置到一個巨大的switch語句之中。其中SIOCDEVPRIVATE命令和其他的在0x89F0到0x89FF之間的程式碼將出現在switch語句中的一個分支——default語句中,程式碼最後還增加了對無線網路的支援。核心執行時會檢查表示裝置的結構變數中,是否已經定義了一個與裝置相關的ioctl控制程式碼(handler)。這裡的控制程式碼是一個函式指標,它在表示裝置的結構變數中do_ioctl部分。如果已經設定了這個控制程式碼,那麼核心將會執行它。如ipv6隧道裝置體,在初始化時,就作了說明:dev->do_ioctl = ip6ip6_tnl_ioctl,其中函式ip6ip6_tnl_ioctl就是該裝置對應的ioctl控制程式碼,由於隧道裝置是自己定義的ioctl命令字,因而執行應在default語句中,進而呼叫到自己定義的ioctl處理函式ip6ip6_tnl_ioctl。

● 使用者態程式與核心態通訊流程分析:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-958333/,如需轉載,請註明出處,否則將追究法律責任。

相關文章