文件描述符在内核态下的一些小把戏
http://bbs.chinaunix.net/thread-1928306-1-1.html
前面的話:
linux環(huán)境:虛擬機(jī)VMware Server上安裝的ubuntu10.4,通過putty登錄shell。
抄書:
文件描述符(file descriptor:fd)是個(gè)簡(jiǎn)單的整數(shù),用以標(biāo)明每一個(gè)被進(jìn)程所打開的文件。
可以通過查看/proc/pid/fd/目錄查看該進(jìn)程的fd。
先從用戶態(tài)開始:
? ? 編寫一個(gè)helloworld,運(yùn)行后通過proc可以看到進(jìn)程helloworld有三個(gè)fd(0,1,2),指向3個(gè)設(shè)備文件,均為/dev/pts/0。
? ? 然后在helloworld中打開一個(gè)文件,查看會(huì)發(fā)現(xiàn)0、1、2沒有變化,另多了一個(gè)fd(3)指向打開的文件。
繼續(xù)抄書,這次是Linux Programmer's Manual:
DESCRIPTION
? ?? ? Under normal circumstances every Unix program has three streams opened for it when it starts up, one for input, one for output, and one for print‐
? ?? ? ing diagnostic or error messages.??These are typically attached to the user's terminal (see tty(4) but might??instead??refer??to??files??or??other
? ?? ? devices, depending on what the parent process chose to set up.??(See also the "Redirection" section of sh(1).)
? ?? ? The input stream is referred to as "standard input"; the output stream is referred to as "standard output"; and the error stream is referred to as
? ?? ? "standard error".??These terms are abbreviated to form the symbols used to refer to these files, namely stdin, stdout, and stderr.
? ?? ? Each of these symbols is a stdio(3) macro of type pointer to FILE, and can be used with functions like fprintf(3) or fread(3).
? ?? ? Since FILEs are a buffering wrapper around Unix file descriptors, the same underlying files may also be accessed using the raw??Unix??file??inter‐
? ?? ? face, that is, the functions like read(2) and lseek(2).
? ?? ? On??program??startup,??the integer file descriptors associated with the streams stdin, stdout, and stderr are 0, 1, and 2, respectively.??The pre‐
? ?? ? processor symbols STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO are defined with these values in <unistd.h>.? ?(Applying??freopen(3)??to??one??of
? ?? ? these streams can change the file descriptor number associated with the stream.)
? ?? ? Note that mixing use of FILEs and raw file descriptors can produce unexpected results and should generally be avoided.??(For the masochistic among
? ?? ? you: POSIX.1, section 8.2.3, describes in detail how this interaction is supposed to work.)??A general rule is that file descriptors??are??handled
? ?? ? in??the??kernel,??while stdio is just a library.??This means for example, that after an exec(3), the child inherits all open file descriptors, but
? ?? ? all old streams have become inaccessible.
? ?? ? Since the symbols stdin, stdout, and stderr are specified to be macros, assigning to them is non-portable.??The standard streams can??be??made??to
? ?? ? refer??to??different??files??with help of the library function freopen(3), specially introduced to make it possible to reassign stdin, stdout, and
? ?? ? stderr.??The standard streams are closed by a call to exit(3) and by normal program termination.
? ? fd(0,1,2)就是常說的stdin、stdout、stderr;用戶態(tài)程序運(yùn)行時(shí)默認(rèn)建立,/dev/pts/0則是運(yùn)行程序時(shí)的終端。
? ? (純粹的內(nèi)核進(jìn)程則不同,后面會(huì)提到)
? ? fd在用戶態(tài)下可以通過函數(shù)dup2()進(jìn)行重定向,而內(nèi)核態(tài)下也有系統(tǒng)調(diào)用sys_dup2(),有興趣的可以試試。
以上算是背景介紹,這里是內(nèi)核版,老是在用戶態(tài)繞未免有點(diǎn)不成體統(tǒng)^0^
進(jìn)入內(nèi)核態(tài),如何獲得文件描述符相關(guān)信息?
1、內(nèi)核函數(shù)fget(),根據(jù)fd號(hào)獲得指向文件的struct file;
2、內(nèi)核函數(shù)d_path(),根據(jù)struct file獲取文件名及路徑;
3、默認(rèn)打開文件最大值(fd最大值):NR_OPEN_DEFAULT。
PS:如果要深究,這些信息存放在struct task_struct中:
把以上東東寫成一個(gè)函數(shù):[code]#include <linux/fs.h>? ?? ?? ???/*struct file*/
#include <linux/file.h>? ?? ?? ?/*fget() fput()*/
#include <linux/fdtable.h>? ?? ?/*NR_OPEN_DEFAULT*/
#include <linux/dcache.h>? ?? ? /*d_path()*/
void KernelFd_ShowFd(void)
{
? ? ? ? int i_Loop = 0;
? ? ? ? char ac_Buf[64];
? ? ? ? char * pc_FdName = NULL;
? ? ? ? struct file * pst_File = NULL;
? ? ? ? printk("\nshow Fd:\n");
? ? ? ? //遍歷FD? ? ? ??
? ? ? ? for (i_Loop = 0; i_Loop < NR_OPEN_DEFAULT; i_Loop++)
? ? ? ? {
? ? ? ? ? ? ? ? //取出FD對(duì)應(yīng)struct file并檢驗(yàn)
? ? ? ? ? ? ? ? pst_File = fget(i_Loop);
? ? ? ? ? ? ? ? if (NULL != pst_File)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? //取出FD對(duì)應(yīng)文件路徑及文件名并檢驗(yàn)
? ? ? ? ? ? ? ? ? ? ? ? pc_FdName = d_path(&(pst_File->f_path), ac_Buf, sizeof(ac_Buf));
? ? ? ? ? ? ? ? ? ? ? ? if (NULL != pc_FdName)
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? printk("\tfd %02d is %s, addr is 0x%p\n", i_Loop, pc_FdName, pst_File);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? printk("\tfd %02d name is NULL, addr is 0x%p\n", i_Loop, pst_File);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? //fget(),fput()成對(duì)
? ? ? ? ? ? ? ? ? ? ? ? fput(pst_File);
? ? ? ? ? ? ? ? }
? ? ? ? }
? ? ? ? printk("\n");
}[/code]進(jìn)行驗(yàn)證,編寫內(nèi)核模塊,在init函數(shù)中加入延時(shí),insmod后通過proc查看fd,和KernelFd_ShowFd()結(jié)論一致。
? ? 看看不同情況下fd指向誰:
? ?? ?1、通過putty登錄,加載模塊,fd(0、1、2)均指向/dev/pts/0;
? ?? ?2、再開啟一個(gè)putty,加載模塊,fd(0、1、2)均指向/dev/pts/1;
? ?? ?3、在ubuntu桌面進(jìn)入shell,加載模塊,fd(0、1、2)均指向/dev/tty1;
? ?? ?4、使用kthread_create建立內(nèi)核態(tài)thread,通過proc查看,fd0指向./,fd1指向../。
? ? insmod加載模塊時(shí),實(shí)際是先啟動(dòng)了進(jìn)程:insmod??XXX.ko;結(jié)果和helloworld一致。
? ? 此時(shí)fd(0、1、2)為stdin、stdout、stderr,而stdin、stdout、stderr都指向當(dāng)前終端,可見通過不同的終端加載模塊,fd指向的設(shè)備文件不同。
? ? 而純粹的kernel thread,其fd既沒有三個(gè),也沒有指向任何設(shè)備文件,因?yàn)閷?duì)它而言,沒有stdin、stdout、stderr。
小把戲之一:
既然fd(0、1、2)均指向當(dāng)前終端,那么,操作一下?
有了fd,如何操作,很自然的就想到了sys_read(),sys_write();但是很不幸,unbutu10.4中sys_XXX沒有導(dǎo)出。
那么研究一下sys_write():
先抱怨一下,系統(tǒng)調(diào)用都用SYSCALL_DEFINE封裝了,查找起來很麻煩。
file_pos_read()和file_pos_write()內(nèi)容很簡(jiǎn)單;
fget_light()和fput_light()比較麻煩,不過好在有兩個(gè)已經(jīng)導(dǎo)出的內(nèi)核函數(shù)fget()、fput()可以代替。
重寫的sys_write()如下:[code]//功能實(shí)現(xiàn)所需頭文件
#include <linux/fs.h>? ?? ?? ???/*struct file
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ???vfs_write()*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ???
#include <linux/file.h>? ?? ?? ?/*fget()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ???fput()*/
#include <linux/uaccess.h>? ? /*get_fs()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?? ???KERNEL_DS
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?? ???set_fs();*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?? ???
long Kprintf_SysWrite(unsigned int Vui_Fd, char * Vstr_buf, unsigned int Vui_BufLen)
{
? ? ? ? long l_Ret = -EBADF;
? ? ? ? struct file * pst_File = NULL;
? ? ? ? mm_segment_t st_FsStatus;
? ? ? ? //參數(shù)檢查,Vui_Fd在fget()中檢查, Vui_BufLen必為非負(fù)
? ? ? ? if (NULL == Vstr_buf)
? ? ? ? {
? ? ? ? ? ? ? ? printk(KERN_ALERT "write buffer is empty!\n");
? ? ? ? ? ? ? ? return l_Ret;
? ? ? ? }
? ? ? ? //設(shè)置文件系統(tǒng)接受內(nèi)核態(tài)地址
? ? ? ? st_FsStatus = get_fs();
? ? ? ? set_fs(KERNEL_DS);
? ? ? ? pst_File = fget(Vui_Fd);
? ? ? ??
? ? ? ? if (NULL != pst_File)
? ? ? ? {
? ? ? ? ? ? ? ? loff_t Tst_Pos = pst_File->f_pos;
? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? l_Ret = vfs_write(pst_File, Vstr_buf, Vui_BufLen, &Tst_Pos);
? ? ? ? ? ? ? ? pst_File->f_pos = Tst_Pos;
? ? ? ? ? ? ? ? fput(pst_File);
? ? ? ? }
? ? ? ? //恢復(fù)文件系統(tǒng)原狀態(tài)
? ? ? ? set_fs(st_FsStatus);
? ? ? ? return l_Ret;
}[/code]這里只實(shí)現(xiàn)了sys_write(),其他關(guān)于文件的系統(tǒng)調(diào)用根據(jù)此思路也可以實(shí)現(xiàn),有興趣的可以試試。
? ? 調(diào)用它,即可將Vstr_buf中內(nèi)容輸出到終端,如果fd指向的不是終端文件呢?
? ? 比如,一個(gè)socket,一個(gè)設(shè)備文件?
? ? 或許可以通過在用戶態(tài)打開,在內(nèi)核態(tài)讀、寫、操作,避免用戶態(tài)——內(nèi)核態(tài)切換對(duì)性能造成的影響。
? ? 當(dāng)然,真要去實(shí)現(xiàn)還有很多后續(xù)工作。
我更感興趣的是下面的東東。
小把戲之二:
更進(jìn)一步,參考printk進(jìn)行封裝Kprintf_SysWrite():[code]//kprintf一次最多打印1024個(gè)字符,1024參考printk()中設(shè)定
#define KPRINTF_MAX 1024
char Gac_KprintfBuf[KPRINTF_MAX] = {0, };
int Kprintf(const char * Vstr_Fmt, ...)
{
? ? ? ? int i_Ret;
? ? ? ? va_list st_Args;
? ? ? ??
? ? ? ? //參數(shù)檢查
? ? ? ? if (NULL == Vstr_Fmt)
? ? ? ? {
? ? ? ? ? ? ? ? printk(KERN_ALERT "Vstr_Fmt is empty!\n");
? ? ? ? ? ? ? ? return -EBADF;
? ? ? ? }
? ? ? ? //清0
? ? ? ? memset(Gac_KprintfBuf, sizeof(Gac_KprintfBuf), 0);
? ? ? ? //組合字符串及其參數(shù)
? ? ? ? va_start(st_Args, Vstr_Fmt);
? ? ? ??
? ? ? ? i_Ret = vsnprintf(Gac_KprintfBuf, KPRINTF_MAX, Vstr_Fmt, st_Args);
? ? ? ? va_end(st_Args);
? ? ? ? //檢查組合后字符串長度
? ? ? ? if (0 < i_Ret)
? ? ? ? {
? ? ? ? ? ? ? ? //為正數(shù)才寫入fd0
? ? ? ? ? ? ? ? i_Ret = (int)Kprintf_SysWrite(0, Gac_KprintfBuf, (unsigned int)i_Ret);
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? ? ? printk("something is wrong with Vstr_Fmt or snprintf\n");
? ? ? ? }
? ? ? ??
? ? ? ? return i_Ret;
}[/code]這樣,和printf的使用完全一樣,內(nèi)核態(tài)程序在可以在shell上顯示信息,
再進(jìn)一步,我們可以實(shí)現(xiàn)一條非常符合使用習(xí)慣的,內(nèi)核與shell直接交互的通道。
其他:
將printk中原始代碼加入Kprintf()中,Kprintf就可以帶有printk功能:[code]int Kprintf_K(const char * Vstr_Fmt, ...)
{
? ? ? ? int i_Ret;
? ? ? ? va_list st_Args;
? ? ? ? va_list st_PrintkArgs;
? ? ? ??
? ? ? ? //參數(shù)檢查
? ? ? ? if (NULL == Vstr_Fmt)
? ? ? ? {
? ? ? ? ? ? ? ? printk(KERN_ALERT "Vstr_Fmt is empty!\n");
? ? ? ? ? ? ? ? return -EBADF;
? ? ? ? }
? ? ? ? //清0
? ? ? ? memset(Gac_KprintfBuf_K, sizeof(Gac_KprintfBuf_K), 0);
? ? ? ? //組合字符串及其參數(shù)
? ? ? ? va_start(st_Args, Vstr_Fmt);
? ? ? ??
? ? ? ? i_Ret = vsnprintf(Gac_KprintfBuf_K, KPRINTF_MAX, Vstr_Fmt, st_Args);
? ? ? ? va_end(st_Args);
? ? ? ? //檢查組合后字符串長度
? ? ? ? if (0 < i_Ret)
? ? ? ? {
? ? ? ? ? ? ? ? //為正數(shù)才寫入fd0
? ? ? ? ? ? ? ? i_Ret = (int)Kprintf_SysWrite(0, Gac_KprintfBuf_K, (unsigned int)i_Ret);
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? ? ? printk("something is wrong with Vstr_Fmt or snprintf\n");
? ? ? ? }
? ? ? ? //原printk打印
? ? ? ? va_start(st_PrintkArgs, Vstr_Fmt);
? ? ? ??
? ? ? ? vprintk(Vstr_Fmt, st_PrintkArgs);
? ? ? ? va_end(st_PrintkArgs);
? ? ? ? return i_Ret;
}[/code]內(nèi)核版曾有帖子提到內(nèi)核態(tài)下如何操作文件,走的是file->f_op->write,按該帖思路實(shí)現(xiàn)對(duì)各個(gè)fd操作代碼如下:
(僅驗(yàn)證用,沒有寫成與Kprintf_SysWrite一致格式)[code]void KernelFd_WriteFd(void)
{
? ? ? ? int i_Loop = 0;
? ? ? ? long l_Ret;
? ? ? ? char * str_WriteString0 = "Test write fd in kernel module\n";
? ? ? ? //char * str_WriteString1 = "Test write fd in kernel module";
? ? ? ? char ac_Buf[64];
? ? ? ? char * pc_FdName = NULL;
? ? ? ? struct file * pst_File = NULL;
? ? ? ? printk("\nfile->f_op->write:\n");
? ? ? ? //遍歷FD? ? ? ??
? ? ? ? for (i_Loop = 0; i_Loop < NR_OPEN_DEFAULT; i_Loop++)
? ? ? ? {
? ? ? ? ? ? ? ? //取出FD對(duì)應(yīng)struct file并檢驗(yàn)
? ? ? ? ? ? ? ? pst_File = fget(i_Loop);? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? if (NULL != pst_File)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? //取出FD對(duì)應(yīng)文件路徑及文件名并檢驗(yàn)
? ? ? ? ? ? ? ? ? ? ? ? pc_FdName = d_path(&(pst_File->f_path), ac_Buf, sizeof(ac_Buf));
? ? ? ? ? ? ? ? ? ? ? ? if (NULL != pc_FdName)
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? printk("\tfd %2d is %s, addr is 0x%p\n", i_Loop, pc_FdName, pst_File);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? printk("\tfd %2d name is NULL, addr is 0x%p\n", i_Loop, pst_File);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? //調(diào)用file->f_op->write進(jìn)行操作
? ? ? ? ? ? ? ? ? ? ? ? printk("\t\twrite '%s' to %s\n", str_WriteString0, pc_FdName);
? ? ? ? ? ? ? ? ? ? ? ? if ((NULL != pst_File->f_op) && (NULL != pst_File->f_op->write))
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? mm_segment_t Tst_FsStatus;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Tst_FsStatus = get_fs();
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? set_fs(KERNEL_DS);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? l_Ret = pst_File->f_op->write(pst_File, str_WriteString0, strlen(str_WriteString0), &(pst_File->f_pos));
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? set_fs(Tst_FsStatus);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? printk(KERN_ALERT "\t\twrite fd %2d return %ld!\n", i_Loop, l_Ret);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? //fget(),fput()成對(duì)
? ? ? ? ? ? ? ? ? ? ? ? fput(pst_File);
? ? ? ? ? ? ? ? ? ? ? ? printk("\n");
? ? ? ? ? ? ? ? }
? ? ? ? }
? ? ? ? printk("\n");
}[/code]
最后附上調(diào)用kprintk和kprintf_k的圖:[code]static __init int Kprintf_init(void)
{
? ? printk(KERN_ALERT "Hello world!\n");
? ? ? ? Kprintf("Hello everybody, I am kprintf!\n");
? ? ? ? Kprintf("Test: %d, %ld, 0x%x, %c, %s!\n", 1024, 1024UL, 1024, 'c', "string");
? ? ? ? Kprintf_K("Hello everybody, I am Kprintf_K!\n");
? ? ? ? Kprintf_K("Kprintf_K Test: %d, %ld, 0x%x, %c, %s!\n", 1024, 1024UL, 1024, 'c', "string");
? ? return 0;
}[/code]
轉(zhuǎn)載于:https://www.cnblogs.com/balaamwe/archive/2012/01/17/2324949.html
總結(jié)
以上是生活随笔為你收集整理的文件描述符在内核态下的一些小把戏的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 回家了。忧伤了。
- 下一篇: 病毒研究之感染linux脚本