系統調用 用戶空間和內核空間之間的通信實現 ● 與系統調用相關的數據結構和函數 系統調用函數名以「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