1。问题描述如何操作内核中的某个文件?问题二、操作函数1、分析在用户态下,读写文件可以通过read和write这两个系统调用来完成(C库函数其实就是对系统调用的封装)。但是内核态并没有这个系统调用,我们怎么读写文件呢?阅读Linux内核源码我们可以知道,sys_read和sys_write这两个函数实际上是在内核执行的时候执行的,但是这两个函数并没有UseEXPORT_SYMBOL来导出,也就是说其他模块是不能使用的。fs/open.c中系统调用的具体实现如下(内核版本3.14):SYSCALL_DEFINE3(open,constchar__user*,filename,int,flags,umode_t,mode){if(force_o_largefile())flags|=O_LARGEFILE;returndo_sys_open(AT_FDCWD,filename,flags,mode);}跟踪do_sys_open()函数,longdo_sys_open(intdfd,constchar__user*filename,intflags,umode_tmode){structopen_flagsop;intfd=build_open_flags(flags,mode,&op);structfilename*tmp;if(fd)returnfd;tmp=getname(filename);if(IS_ERR(tmp))returnPTR_ERR(tmp);fd=get_unused_fd_flags(flags);if(fd>=0){structfile*f=do_filp_open(dfd,tmp,&op);if(IS_ERR(f)){put_unused_fd(fd);fd=PTR_ERR(f);}else{fsnotify_open(f);fd_install(fd,f);}}putname(tmp);returnfd;}会发现主要是用到do_filp_open()函数这个函数在fs/namei.c中,structfile*do_filp_open(intdfd,structfilename*pathname,conststructopen_flags*op){structnameidatand;intflags=op->lookup_flags;structfile*filp;filp=path_openat(dfd、路径名、&nd、op、标志|LOOKUP_RCU);如果(不太可能(filp==ERR_PTR(-ECHILD)))filp=path_openat(dfd,pathname,&nd,op,flags);if(unlikely(filp==ERR_PTR(-ESTALE)))filp=path_openat(dfd,pathname,&nd,op,flags|LOOKUP_REVAL);returnfilp;}这个函数最终打开文件并返回文件类型指针所以我们只需要找到其他调用do_filp_open()函数的地方,就可以找到我们需要的文件操作函数。在文件fs/open.c中,filp_open函数还调用了file_open_name函数,/***filp_open-openfileandreturnfilepointer**@filename:pathtoopen*@flags:openflagsaspertheopen(2)secondargument*@mode:modeforthenewfileifO_CREATisset,elseignored**Thisisthehelpertoopenafilefromkernelspaceifourreally*必须。但通常你不应该这样做,所以请继续前进,这里没什么可看的..*/structfile*filp_open(constchar*filename,intflags,umode_tmode){structfilenamename={.name=filename};returnfile_open_name(&name,flags,mode);}EXPORT_SYMBOL(filp_open);函数file_open_name调用do_filp_open,接口和sys_open函数很相似,调用参数和sys_open一样,都是用EXPORT_SYMBOL导出的,所以可以用这个函数在内核中打开文件,功能与应用层的打开非常相似。/***file_open_name-openfileandreturnfilepointer**@name:structfilenamecontainingpathtoopen*@flags:openflagsaspertheopen(2)secondargument*@mode:modeforthenewfileifO_CREATisset,elseignored**Thisisthehelpertoopenafilefromkernelspaceifyoureally*haveto.Butingenerallyyoushouldnotdothis,sopleasemove*along,nothingtoseehere..*open/structfile*structfilename*name,intflags,umode_tmode){structopen_flagsop;interr=build_open_flags(flags,mode,&op);returnerr?ERR_PTR(err):do_filp_open(AT_FDCWD,name,&op);}2.所有的操作函数都用同样的方法,找到内核中产生了一组操作文件的函数,如下:这些函数的参数和应用层的文件IO函数很相似,open,read,write,close。3、用户空间地址虽然我们找到了这些函数,但是我们不能直接使用它们。因为在vfs_read和vfs_write函数中,参数buf指向的用户空间的内存地址,如果我们直接使用内核空间的指针,会返回-EFALUT。这是因为使用的缓冲区超出了用户空间的地址范围。一般的系统调用都会要求你使用的缓冲区不能在内核区。这可以通过set_fs()和get_fs()来解决。在include/asm/uaccess.h中,有如下定义:#defineMAKE_MM_SEG(s)((mm_segment_t){(s)})#defineKERNEL_DSMAKE_MM_SEG(0xFFFFFFFF)#defineUSER_DSMAKE_MM_SEG(PAGE_OFFSET)#defineget_ds()(KERNEL_DS)#defineget_fs((current->addr_limit)#defineset_fs(x)(current->addr_limit=(x))如果使用,可以按如下顺序执行:mm_segment_tfs=get_fs();set_fs(KERNEL_FS);//vfs_write();//vfs_read();set_fs(fs);详细解释:系统调用本来是提供给用户空间的程序访问的,所以,对于传递给它的参数(比如上面的buf),会认为是来自用户空间默认,在read或write()函数中,为了保护内核空间,get_fs()获取的值一般用来和USER_DS进行比较,防止用户空间程序“故意”破坏内核空间,而现在要在内核空间使用系统调用,此时传递给read或write()的参数地址就是addres内核空间的s,在USER_DS(USER_DS~KERNEL_DS)之上,如果不做任何其他处理,在write()函数中,会认为地址超出了USER_DS的范围,所以会被认为是“故意破坏”用户空间,不允许进一步执行。为了解决这个问题,set_fs(KERNEL_DS)将它的可访问空间限制扩大到KERNEL_DS,这样系统调用就可以在内核中顺利使用了!在VFS的支持下,用户态进程可以读写任何类型的文件系统使用read和write这两个系统调用,但是linux内核中没有这个系统调用。我们如何操作文件?我们知道read和write实际上是在进入内核态后执行了sys_read和sys_write,但是查看内核源码,发现这些操作文件的函数都没有导出(exportedusingEXPORT_SYMBOL),也就是说不能被导出在内核模块中使用,那么我们应该怎么做呢?通过查看sys_open的源码,我们发现它主要使用了do_filp_open()函数。这个函数在fs/namei.c中,在修改后的文件中,filp_open函数也间接调用了do_filp_open函数,接口和sys_open函数很相似,调用参数和sys_open一样,都是使用EXPORT_SYMBOL导出,所以我们猜测该函数可以打开文件,功能与open相同。三、实例Makefileifneq($(KERNELRELEASE),)obj-m:=sysopen.oelseKDIR:=/lib/modules/$(shelluname-r)/buildPWD:=$(shellpwd)all:$(info"1st")make-C$(KDIR)M=$(PWD)modulesclean:rm-f*.ko*.o*.mod.o*.symvers*.cmd*.mod.c*.orderendifsysopen.c#include
