歡迎您光臨本站 註冊首頁

liunx用戶空間和內核空間之間的通信實現(在PPC下的實現)

←手機掃碼閱讀     火星人 @ 2014-03-26 , reply:0

系統調用
用戶空間和內核空間之間的通信實現

● 與系統調用相關的數據結構和函數
系統調用函數名以「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。

● 用戶態程序與內核態通信流程分析:

[火星人 ] liunx用戶空間和內核空間之間的通信實現(在PPC下的實現)已經有1007次圍觀

http://coctec.com/docs/linux/show-post-190002.html