具体到文件名乱码的问题,需要明确两点

  1. 第一,文件名作为一个字符串,需要被编码后存入文件系统;
  2. 第二,Linux内核无非是个特殊的应用程序,它读取文件名,再把文件名以编码后的形式传递出去。

NLS,对应于Windows的codepage。

在对文件名的处理上,fat和vfat的区别在于:fat/msdos只支持短文件名(8.3命名法),而vfat加入了对长文件名和UNICODE的支持。

为了保持与fat的兼容性,在vfat中,每个文件同时拥有“长”文件名和短文件名,其中短文件名不区分大小写(实际上是不允许小写字母出现在文件 名中)。可以这么理解,对于vfat,“长文件名”是文件真正的名字,“短文件名”则是提供兼容性的名字。举例来说,文件“真名”为abc.txt,它的 短文件名是ABC.TXT;文件“真名”为alongname.txt,它的短文件名则是ALONGN~1.TXT。

短文件名按codepage编码存储,长文件名按UNICODE编码存储。因此,如果文件的真名(也就是长文件名)是s,短文件名是s′,则它们在文件系统中分别被存储为

s,φenc(s′).


为了访问fat/vfat文件系统,我们需要用内核的msdos或vfat模块。它们有三个跟字符集有关的内核参数:codepage,iocharset,utf8。我们来确定它们对应着什么样的编码或解码函数。

  • φ−1enc(因为短文件名被codepage编码了),解码的结果是短文件名被用unicode表示;
  • φenc,使得内核最终的输出是经过φenc编码的;
  • utf8:几乎等于iocharset=utf8

这样,参数有如下的具体选择:

codepage=936
  • 若locale不是utf8,则选择相应的locale,比如GB系就应该选
iocharset=cp936
  • 若locale是utf8的,可以选择
iocharset=utf8

但这样做的缺点是会导致vfat模块将允许短文件名使用小写字母,这与windows是不兼容的(使用iocharset=utf8时,内核会出一条警告信息的——这比较没道理,估计是个历史遗留问题);

  • utf8:一个解决办法是使用utf8参数,只要iocharset!=utf8,vfat就会处理大小写的问题;而utf8参数则会最终处理字 符集的转换。utf8参数只能显式地通过mount调用(不能在编译内核时预先指定,当然也可以写在/etc/fstab里),我的意思是这个选项不能偷 懒不写。

最后是结论:

  1. codepage=936,这一选项可以在内核默认设置;
mount -t vfat -o iocharset=cp936
mount -t vfat -o iocharset=utf8
mount -t vfat -o iocharset=cp936,utf8

实际上只要内核默认的iocharset不是utf8,直接写

mount -o utf8

就可以,这里iocharset=xxx的作用仅仅是处理大小写,所以怎么填都没关系。

s,其短文件名为s′

  1. s,短文件名被存为φcp936(s′);
  2. φ−1cp936作用于φcp936(s′),得到 φ−1cp936∘φcp936(s′)=s′;
  3. φutf8作用于s,s′,最终向应用程序输出 φutf8(s′),φutf8(s);
  4. φ−1utf8作用于内核输出,得到我们所看见的字符串s,s′.