⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 漫谈兼容内核之三:kernel-win32的文件操作.txt

📁 漫谈系统内核内幕 收集得很辛苦 呵呵 大家快下在吧
💻 TXT
📖 第 1 页 / 共 2 页
字号:

INIT_LIST_HEAD(&wfc->wfc_accessors);
spin_lock_init(&wfc->wfc_lock);
wfc->wfc_myself = obj;

return 0;
} /* end FileControlConstructor() */[/code]
    显然,这个函数毫无特殊之处,而且比前面的FileConstructor()简单多了。这当然是因为许多功能和特性尚未实现的缘故,而并不是本来就应该这么简单。
    回到FileConstructor()的代码中,还有FileCheckSharing()是值得一提的。这个函数根据CreateFileA()的调用参数dwDesiredAccess和dwShareMode检查所要求的访问模式(例如GENERIC_READ和GENERIC_WRITE)和共享模式(例如FILE_SHARE_READ)是否与目标文件已有的各个“打开”相容。具体地说,就是扫描目标文件的WineFileControl结构中的wfc_accessors队列,与队列中各个WineFile结构中记载的访问模式和共享模式进行比较,以确定是否相容。

    创建并/或打开了文件以后,就可以对文件进行各种操作了,最重要的当然是读/写。所以我们就来看看读文件操作的实现。在Windows上,读文件是通过系统调用NtReadFile()进行的。在Kernel_win32的代码中,这个系统调用在内核中的实现就是ReadFile()。同样,调用参数都包装在一个数据结构中:

[code]struct WiocReadFile {
HANDLE   __pad__   hFile;
LPVOID    __pad__   lpBuffer;
DWORD    __pad__   nNumberOfBytesToRead;
LPDWORD   __pad__   lpNumberOfBytesRead;
LPOVERLAPPED  __pad__   lpOverlapped;
};[/code]
    前面三个参数的用途是不言自明的。参数lpNumberOfBytesRead则是个指针,用来返回实际读出的字节数。显然这与Linux的sys_read()不同,因为后者是把它作为函数值返回的。另一个参数lpOverlapped也是个指针,用于异步的读文件操作。所谓异步,是指调用者并不(睡眠)等待操作的完成,而是把访问的要求交给内核以后就先返回干别的事,内核在完成操作以后再予以“回叫(callback)”。但是Kernel-win32尚未实现此项功能。
    下面我们看ReadFile()的代码。

[code]int ReadFile(struct WineThread *thread, struct WiocReadFile *args)
{
struct WineFile *wf;
struct file *file;
Object *obj = NULL;
int ret;

kdebug("ReadFile(%p,%p)\n",thread,args->hFile);

ret = 0;
ret = put_user(ret,args->lpNumberOfBytesRead);
if (ret<0)
  goto cleanup_nobj;

obj = GetObject(thread,args->hFile,&file_objclass);
if (IS_ERR(obj))
  return PTR_ERR(obj);

wf = obj->o_private;
file = wf->wf_file;

ktrace("*** [%d] ReadFile(%p,%p,%u)\n",
        current->pid,obj,
        args->lpBuffer,
        args->nNumberOfBytesToRead
        );

/* check the file can actually be read */
ret = -EACCES;
if (!(wf->wf_access & GENERIC_READ))
  goto cleanup;

ret = -EBADF;
if (!(file->f_mode & FMODE_READ))
  goto cleanup;

ret = -EINVAL;
if (!file->f_op || !file->f_op->read)
  goto cleanup;

/* check that the area isn't mandatorally locked */
ret = locks_verify_area(FLOCK_VERIFY_READ, file->f_dentry->d_inode,
    file, file->f_pos, args->nNumberOfBytesToRead);
if (ret<0)
  goto cleanup;

/* try and read */
ret = file->f_op->read(file, args->lpBuffer, args->nNumberOfBytesToRead, &file->f_pos);
if (ret<0)
  goto cleanup;

ktrace("*** [%d] ReadFile(%p) read %d bytes\n",current->pid,obj,ret);

put_user(ret,args->lpNumberOfBytesRead);

ret = 0;
    cleanup:
objput(obj);

    cleanup_nobj:
ktrace("*** [%d] ReadFile(%p) = %d\n",current->pid,obj,ret);
return ret;
} /* end ReadFile() */[/code]
    这个函数的逻辑是很简单的:先根据Handle通过当前线程的打开对象表找到目标对象,并进一步找到其WieFile数据结构,指向目标文件的struct file结构的指针就保存在WieFile数据结构中;然后在Wine和Linux文件系统两个层面上检查访问权限;最后通过Linux文件系统实施具体的读出。这个过程清楚地表明Kernel-win32是怎样把Windows的文件操作嫁接到Linux文件操作上的。读操作如此,写操作也是大同小异。
    再看“关闭文件”的操作。Windows并没有专门针对文件的“关闭文件”操作,而只有“关闭Handle”操作,因为文件只是“对象”中的一种。

[code]int CloseHandle(struct WineThread *thread, struct WiocCloseHandle *args)
{
struct WineProcess *process;
Object **ppobj, **epobj, *obj;
int last;

ktrace("*** [%d] CloseHandle(%p,%p)\n",
        current->pid,thread,args->hObject);

/* validate the handle */
if (args->hObject<MINHANDLE ||
     args->hObject>=MAXHANDLE ||
     ((__u32)args->hObject & (sizeof(Object*)-1))
     )
  return -EINVAL;

process = GetWineProcess(thread);

write_lock(&process->wp_lock);
ppobj = (Object**)
((char*)&process->wp_handles + (__u32)args->hObject – sizeof(Object*));
obj = *ppobj;
*ppobj = NULL;

/* see if this was the last attachment from this "process" */
epobj = &process->wp_handles[MAXHANDLES];
last = 1;
if (obj && obj->o_class->detach) {
  for (ppobj=process->wp_handles; ppobj<epobj; ppobj++) {
   if (*ppobj==obj) {
    last = 0; /* yes */
    break;
   }
  }
}

write_unlock(&process->wp_lock);

if (!obj)
  return -EBADF;

if (last && obj->o_class->detach)
  obj->o_class->detach(obj,process); /* last attachment gone */

objput(obj);
return 0;
} /* end CloseHandle() */[/code]

    开始时在所属进程的打开对象表中搜索,根据Handle找到目标对象的过程跟前面ReadFile()中调用的GetObject()基本相同,不同的是这里要把打开对象表中的相应表项写成NULL,以断开跟目标对象的连系。接下去的意图很明显,就是检查这是否当前进程对目标对象的最后一个Handle,即是否对目标对象的最后一个打开,如果是就调用该类对象的detach操作(注意代码中的注释“yes”意思反了)。可是,看一下文件类对象的数据结构file_objclass,就可以发现它并没有提供detach函数,所以实际上并不会执行。看来,在kernel-win32的设计中,关闭文件类对象时并不需要detach操作。然而在关闭对象时总得对目标对象做点什么啊,否则其数据结构所占的空间何时才能释放?这就是下面objput()要做的事。

[code][CloseHandle() > objput()]

/*
* decrement an object's usage count
* - when usage count reaches zero, the object is destroyed
* - uses the class's rwlock to govern access to the count
*/
void objput(Object *obj)
{
ktrace("objput(%p)\n",obj);

if (!obj) return;

#ifdef OBJECT_MAGIC
if (obj->o_magic!=OBJECT_MAGIC)
  panic("bad object magic\n");
#endif

write_lock(&obj->o_class->oc_lock);
if (!atomic_dec_and_test(&obj->o_count))
  goto still_in_use;

#ifdef OBJECT_MAGIC
obj->o_magic = 0x01010101;
#endif

list_del(&obj->o_objlist);
obj->o_class->destructor(obj);

write_unlock(&obj->o_class->oc_lock);

/* quick insanity check */
if (waitqueue_active(&obj->o_wait)) {
  printk(KERN_ALERT "wineserver:"
         " object being deleted is still being waited upon\n");
  return;
}

if (obj->o_name.name) putname(obj->o_name.name);
poison(obj,sizeof(Object));
kfree(obj);
return;

    still_in_use:
write_unlock(&obj->o_class->oc_lock);
} /* end objput() */[/code]

    首先通过atomic_dec_and_test()递减目标对象的引用计数、并测试其结果。如果尚未达到0,就说明目标对象还有用户,所以就不需要有进一步的处理。否则,要是计数达到了0,那就要把它从对象队列中删除,并调用该类对象的destructor函数。文件类对象的destructor函数是FileDestructor()。

[code][CloseHandle() > objput() > FileDestructor()]

/*
* destroy a file (discard its private data)
*/
static void FileDestructor(Object *obj)
{
struct WineFile *wf = obj->o_private;

kdebug("FileDestructor: f_count=%d i_count=%d i_wc=%d\n",
        atomic_read(&wf->wf_file->f_count),
        atomic_read(&wf->wf_file->f_dentry->d_inode->i_count),
        atomic_read(&wf->wf_file->f_dentry->d_inode->i_writecount)
        );

ktrace("FileDestructor(%p)\n",obj);

/* discard my association with the Linux file */
fput(wf->wf_file);

/* unlink from the controlling object */
list_del(&wf->wf_ctllist);
obj->o_name.name = NULL;
objput(wf->wf_control->wfc_myself);

poison(obj->o_private,sizeof(struct WineFile));
kfree(obj->o_private);

} /* end FileDestructor() */[/code]

    先通过fput()切断与Linux文件系统中目标文件struct file结构的连系,然后将WineFile数据结构从目标文件控制对象的队列中删除,再对目标文件控制对象也实施objput(),最后释放WineFile数据结构。在释放前还调用了一个函数poison(),那只是将WineFile数据结构中的内容破坏掉。注意前面objput()中也调用了poison()和kfree(),那是释放目标对象的Object数据结构,而这里释放的是与之配套的WineFile数据结构。

    从上述创建文件、读文件、和关闭文件这三个文件操作的代码中,我们可以体会到:“内核差异内核补”、把Windows的文件操作嫁接到Linux文件操作上面、其逻辑和思路是多么简单明了,这与Wine试图在核外解决问题的做法形成了鲜明的对比。虽然本文没有涉及写文件操作的代码,但是读者想必明白那是大同小异。
    更重要的是,与Wine相比,逻辑上和操作上的这种简化和优化必将大大提高文件操作的效率。
    但是,另一方面,我们也应看到,Kernel-win32只是朝正确的方向走了一小步,迄今所实现的只是系统调用界面的一个很粗糙、很不完整的骨架,离实用还相距甚远,而且许多具体的设计和实现也值得推敲。
    在CreateFileA()的过程中,我们在前面FileConstructor()的代码中可以看到调用参数之一lpSecurityAttributes根本就没有被引用;另一个参数dwFlagsAndAttributes虽然被引用了,但只是检查了其中的“只读”标志位。然而这两个调用参数恰恰是很重要的。例如lpSecurityAttributes是个指针,应该指向一个SECURITY_ATTRIBUTES数据结构,Windows DDK中有这个数据结构的定义:

[code]typedef struct _SECURITY_ATTRIBUTES {
    DWORD nLength;
    LPVOID lpSecurityDescriptor;
    BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;[/code]

    可见,这个数据结构中不光有关于(所创建)目标文件安全特性的描述,还规定了这个文件本次打开后的上下文是否可遗传。尽管许多应用程序在调用CreateFileA()时会对此给出一个NULL指针,表示采用默认的行为特征,但是有些应用程序却会利用这个参数。忽略这个参数无疑有可能会使那样的应用程序产生一些令人莫名其妙的结果。
    再看dwFlagsAndAttributes,这个参数也绝不只是一个FILE_ATTRIBUTE_READONLY标志位那么简单。例如其中有个标志位是FILE_FLAG_DELETE_ON_CLOSE,表示在关闭这个文件时应将其删除。再例如FILE_ATTRIBUTE_COMPRESSED也是个标志位,其作用不言自明。显然,Kernel-win32只考虑了其中的FILE_ATTRIBUTE_READONLY,而留下了大量其它的标志位未作处理,甚至在数据结构中也没有为保存这些成分留下空间。实际上,实现那些标志位的工作量可能远远超过Kernel-win32迄今已有的实现。
    此外,ReadFile()的参数lpOverlapped也没有被用到,这是用来实现异步操作的。Linux内核也支持异步文件操作,但是显然这里还没有考虑把它嫁接过去。
    还有特别值得一提的是已打开对象的遗传问题。显然Kernel-win32对此还没有考虑,因为否则在Object结构中(或者别的什么数据结构中)总得有个地方来保存有关的信息。
    最后还要提一下跨进程的Handle复制。我在以前的漫谈中也曾讲到,只有在内核中才能有效率地实现这个功能,但是我们并没有看到Kernel-win32在做这件事的迹象。
    不过,尽管差距还很大,由于Kernel-win32毕竟是在内核中弥补这些差距,所以原则上并不存在大的障碍,只是还没有来得及做、或者是否准备做的问题。

    实际上,这正是所谓“20/80原理”的一个例证。就文件操作而言,有了打开/创建、读、写、移动读写位置这些基本功能,许多应用程序就可以运行了。实现这些基本功能的工作量和复杂性可能只是20%,而受惠的应用软件可能是80%。但是要让剩下来的那20%也能运行起来,所要求的工作量和复杂性则可能有80%。所以前面那20%的工作当然应该优先。然而,想要达到“高度兼容”的目的,是绝不能置那20%于不顾的。就像艺术表现的真实性往往在于细节一样,兼容程度的高低也往往深受细节的影响。而且,有些“细节”考虑甚至可能会导致推翻原来的设计方案。例如关于跨进程复制Handle的考虑、关于遗传方式的考虑、就使得Wine实际上不可能达到高度兼容,至少是不可能达到比较有效率的高度兼容(更何况还有设备驱动的问题)。而Kernel-win32,说是对Wine的优化,实质上就是推翻了Wine原来的设计方案。好在Kernel-win32的工作已经做到了内核中,从大的方面来说,总体的设计方案已经不太会因为某些细节不好实现而被推翻了,但是一些具体的设计、具体的实现、则仍是可以推敲和改变的。
    对于兼容内核,Kernel-win32要实现的相当于它的一个子集,即其系统调用界面。因此兼容内核理应从Kernel-win32吸取营养,甚至直接“拿来”。不过由于Kernel-win32还很很粗糙、很不完整,一些具体的设计和实现也值得推敲,所以能直接“拿来”的大概不多。倒是它对于各种成分的轻重缓急和实现次序方面的考虑,还是值得、也应该借鉴的。

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -