译文:非官方的linux开发FAQ Unofficial comp.os.linux.development.* FAQ
看到这篇文章,很适合linux入门者。故把它翻译过来,练练英语,呵呵!初次翻译,有不当的地方请多指教,一起交流!我的Email:xiasound@gmail.com QQ:454052811
免责声明:所有使用从这页获得的信息和代码,由自己负责。如果你发现了什么问题,你可以email给我,但是我不能保证一定能帮得上忙。
在这里,我会试着回答几个问题,这些问题我经常在论坛comp.os.linux.development.apps 和 comp.os.linux.development.system.上看到 。如果哪位知道针对此新闻组的官方的FAQ,请告诉我。这个网页迫切需要清除和重新组织,我只是感觉我现在没有时间马上处理。如果你有些建议或想补充缺失的答案,同样欢迎你email我。
你是通过google找到此文的吗?我在服务器日志上看到很多人是这样找到的。你们其中的一些会在这里找到你要的答案,另外一些则不会。如果你翻遍整个网页也没有看到你要的答案,明显的下一步你可以在两新闻组中提问(comp.os.linux.development.apps 和 comp.os.linux.development.system.)。如果你读完此页有额外的问题,那么欢迎你提问,除非你提问了,否则我不能回答。不要做这样的家伙,他第一次想知道kernel_thread返回值的信息而找到此页,得到信息它和sys_fork的返回值是一样的,三个小时后他回来寻找sys_fork返回值的信息。他寻找到一个我可以在五分钟内回答的答案,真的浪费三个小时!
除非有特殊说明,此页的代码片段皆出于我手,它们受保护于GPL 2.
1.我可以从哪里下载Linux?
http://kernel.org/#newtolinux
http://google.com/linux?q=distributions
http://lwn.net/Distributions/
2.我想写一个内核模块,我该从哪里入手呢?
http://www.tldp.org/LDP/lkmpg/mpg.html
http://www.kernelnewbies.org/
http://www.xml.com/ldd/chapter/book/index.html
http://www.oreilly.com/catalog/linuxdrive2/
http://www.kerneltraffic.org/kernel-traffic/kt20050605_312.html#8
3.你能推荐我一些关于内核黑客的书或信息来源吗?
这些建议由Daniel Versick提供:
*如果你想全面的理解Linux内核的算法和数据结构,这本书是一个不错的选择。
"Understanding the Linux Kernel"
by Daniel P. Bovet & Marco Cesati
2001 O'Reilly & Associates Inc.
ISBN 0-596-00002-2
*为了实现一个Linux设备驱动程序:
"Linux Device Drivers" - 2nd edition
by Alessandro Rubini & Jonathan Corbet
2001 O'Reilly & Associates Inc.
ISBN 0-596-00008-1
同样在网络上可以看到: http://www.xml.com/ldd/chapter/book/index.html
*同样是一本关于Linux内核算法和数据结构的书
"Linux Kernel Internals"
by Michael Beck, Harald Bohme, Mirko Dziadzka, Ulrich Kunitz, Robert
Magnus, Dirk Verworner
Addison Wesley
ISBN 0201331438
*如果你想找一本关于Linux网络实现的书,试试这本((but it is AFAIK only available in German at the moment): );
"Linux Netzwerkarchitektur"
by Klaus Wehrle, Frank Paehlke, Hartmut Ritter, Daniel Mueller and Marc Bechler
2002 Addison Wesley
ISBN 3-8273-1509-3
4我可以在哪里找到在线的man pages?
这是其中的几个
http://www.cwi.nl/~aeb/linux/man2html/
http://linux.ctyme.com/
5.为什么我要加载的内核模块时,会提示未解决的符号?
*忘记包含一个定义宏或者一个内联函数的头文件;
*你未使用优化选项,一些内核头文件需要开启优化选项才能正常工作。在gcc命令行里 使用-O2开启优化,一些内联函数需要这样,检测编译错误,去除一些虚拟引用。
*该符号在内核中未定义。注意大多数c标准库函数不能在内核中引用。
*该符号定义了,但是没有被导出以允许其他模块引用。一个符号只有在内核的某个地方导出,才能允许内核的其他模块引用。大多数符号是由kernel/ksyms.c导出的。注意符号只能由定义它的模块本身导出。
*编译的内核和模块有不同的modversions设置。
6.什么是 modversions?
http://www.kernelnewbies.org/faq/#compmod.xml
7.我的printk语句输出到哪儿了?
如果你使用文本模式的VC,会直接输出到屏幕上。如果你使用X,不会出现这种情况;大多数情况调试内核模块最好不要使用X界面。如果你坚持使用X,可以从如下的地方找到输出。
*你可以使用虚拟控制台,显示信息;
*你可以读取系统日志文件。通常日志文件命名为/var/log/messages.通常使用命令"tail -f /var/log/messages"可以单独的显示到一个终端上。
*你可以使用"dmesg"命令;
*你可以使用命令"cat /proc/kmsg".;
*你可以使用一个串口终端,这需要两台电脑。
注意 日志文件名称取决于发行版:
- Red Hat (还有 Mandrake 或其他) 使用 /var/log/messages
- Debian 使用 /var/log/syslog
- 还有一些发行版使用/var/log/kern.log.(According to this posting some distributions use /var/log/kern.log. );
日志是否输出到屏幕取决于消息的重要性。它们被定义了8个级别,级别0是最重要的,仅当系统要崩溃的时候使用。最不重要的是级别7,它通常被用来调试输出;消息的级别取决于每行的开始的三个字符,它们定义在 <linux/kernel.h> ;如果没有指定消息级别,我们会使用一个默认的级别。内核有一个变量指定多么重要的信息必须在屏幕上直接登录。通常klogd进程,将改变这个变量,使更少的消息使得它在屏幕上,但所有的信息将被发送syslgd过程记录到文件中。
很高兴你发问,大多数新手以为它们相同而没有发问。
内核模块是一段放置在内核地址空间代码片段;类似于共享库。模块中可以包含代码和数据,代码可以被任何内核线程访问。当模块加载的时候,一个特殊的初始化代码将被调用,并快速的返回成功或失败,当模块被卸载的时候,指定的卸载函数将被调用,做一些清理函数。
内核线程是一个不包含用户地址空间的进程当一个进程运行在内核模式是与通常的进程有着一些不同,它们共享所有的内核空间,如果一个模块注册了一个函数在内核空间它可以被任何进程甚至多个进程同时调用;
9. 我可以从一个内核模块中启动一个内核线程吗?
你可以这样做。但大多数内核模块并不需要开启内核线程。你在写一个驱动时,如果你想开启一个内核线程,这样其实容易犯错。不管怎样,如果你发现你真的需要一个内核线程你可以轻松的使用函数kernel_thread开启;
#include <fixme> extern int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags);
该函数会启动一个线程,函数的返回值定义情况是和函数sys_fork一样。返回一个正值代表生成线程id,如果返回的是一个负值代表发生了某种错误。返回0是不可能的,因为子进程…(这句不知怎么翻译:because the child terminates after calling fn and does not return to the caller of kernel_thread.The action performed by the new thread is equivalent to the statement exit(fn(arg));. )新线程的行为相当于执行了语句exit(fn(arg));. 参数arg通常指向一个结构体,结构体会在新线程中用到,确保当线程用到时,结构体是存在的。如果结构体是一个局部变量,函数调用完kernel_thread可能在读取结构提前已经退出了,我们要避免这一点。如果不需要该结构体,我们通常在该位置填充NULL。在一个新的内核线程了你通常需要去做的是:
int my_thread(void *arg) { daemonize("kmyd"); while(1) { /* Insert your own code here */ } }
像其他进程一样,内核线程会僵死直到它的父进程发现。如果一个内核线程由一个初始化函数产生,他的父进程就是这个模块的加载部分。如果模块的加载部分终止,该进程会以init作为其新的父进程,在这种情况下init会照顾它直到其终止运行。在其他的情况下,你需要保证僵尸进程的处理。内核线程不能像用户态中那样忽视它的子进程,当用户模式下的程序忽视了它的子进程当出现僵死情况是内核会告诉它,但在内核态没有谁会提醒你。卸载一个使用内核线程的模块而保证不产生竞态。最安全的方式是不卸载模块。在模块初始化部分调用MOD_INC_USE_COUNT从而使模块使用计数为1.在任何地方也不要改变这个内核计数。举一个可以卸载的例子,我们可以查看内核关于usb驱动的例子linux/drivers/usb/hub.c. 它同样有着它的竞争条件,只不过是被恰当的处理了。这里给一个另外的例子(未测试),我相信是可以避免竞态的:
Removing a module using kernel threads is very difficult to do without creating race conditions. The safest solution is to create a module that cannot be removed. In the initialization call MOD_INC_USE_COUNT to get a usecount of 1. Don't change the usecount anywhere else. For an example that can be removed look on the kernel usb driver linux/drivers/usb/hub.c. It has also had its race conditions, but they have presumably been fixed. Here goes another (untested) example which I believe is race free:
static DECLARE_COMPLETION(mythread_exited); static atomic_t time_to_quit = ATOMIC_INIT(0); int my_thread(void *arg) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) daemonize("kmyd"); #else daemonize(); strcpy(current->comm, "kmyd"); #endif while(!atomic_read(&time_to_quit)) { /* Insert your own code here */ } complete_and_exit(&mythread_exited, 0); } static void __exit exit_mymodule(void) { atomic_inc(&time_to_quit); /* Insert code here to wake up the thread if necessary */ wait_for_completion(&mythread_exited); }
10. 我可以在一个内核模块中读写一个文件吗?
可以,但有一点棘手。你不能使用像open, read, write, and close用户空间的函数,取代之的是函数use filp_open 和返回的结构体方法。时刻记住做清除工作。这儿有个例子: (FIXME: Put the updated version here) kcp.c.如果你需要更多的例子建议你看看the khttpd 或 tux webserver的源码。时刻记着只能在进程的上下文访问文件。在内核中访问文件,要时刻警惕你的设计。通常情况下这不是一个好主意。内核代码通常不适用配置文件,取代的是传递参数给内核或模块加载器.如果实现的功能比较复杂,可以在用户空间写一个程序解析配置文件将结果以一个适当的方式传递给内核(比如通过一一系列的调用建一个内核结构体)iptables-restore 就是这样一个例子。通常一个驱动传输数据到硬件可以引用头文件中定义的字符数组。往往是一个驱动需要转移到设备的固件存储在头文件中的字符数组。这通常是初始化数据,所以之前启动/ sbin / init的内存被释放的权利。如果你想从文件中加载固件,也可以做一个用户模式工具。如果你仍然想在内核中访问文件,至少不硬编码的路径。文件名可以通过内核或模块加载器的参数。这通常是在初始化数据,发生在/sbin/init 启动前.如果你想从一个文件加载固件,你可以在用户层写个工具实现。如果你一定要在内核中访问文件,至少不要指定文件名。你可以将文件名作为参数传递给内核或模块加载器。
11.我可以在模块中添加一个系统调用吗?
简短的说: NO
详细点来说:在一些内核版本中,我们可能通过sys_call_table修改和添加系统调用。但是因为此表不能在运行的时候修改,它不受保护,修改此表有可能导致竞态。即使不考虑竞态,当我们在使用或压栈的时候卸载模块,也会产生问题。因为模块会修改sys_call_table,在新内核中该符号不会被一直导出。换句话说,如果你在加载一个模块的时候得到一个这样的提示"unresolved symbol sys_call_table",它的意思是说模块中由一个bug,内核不会接受有bug的模块。
12. 我可以在内核或模块中使用c++吗?
不能,可以在kernel mailing list FAQ.中读到更多消息。
13.linux和cpu关系好吗? 这段不知道咋翻译~
Does Linux have CPU affinity?
The answer below is mostly outdated. In kernel version 2.6 user mode processes can use the sched_setaffinity and sched_getaffinity system calls. There is a patch which backports them to 2.4.
All recent Linux versions will try to keep processes as long time as possible on the same CPU. But processes will be moved if the CPUs are not equally loaded. True CPU affinity was introduced in 2.4.0, but is only available in kernel mode. It can be used from kernel modules, but was not used by the kernel itself. Starting in 2.4.7-pre5 it is used by ksoftirqd to start one instance for each CPU. In 2.5.8-pre3 a systemcall to set CPU affinity was introduced.
I have written a module implementing a userspace interface for the CPU affinity in 2.4.x kernels. WARNING: this is untested code cpus_allowed.tgz
14.我怎样才能使用大于2GB的文件?
在32位的平台上想使用大于等于2GB的文件,你需要传递标志O_LARGEFILE给系统调用open。如果哪位兄弟知道该宏定义在那个头文件,请告诉我。同时,你可以使用如下的代码:
#ifndef O_LARGEFILE #define O_LARGEFILE 0100000 #endif
可以像这样使用open系统调用:
fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE,0666);
如果你想在标准输入输出时使用大文件,你需要一个其中使用O_LARGEFILE标志调用open的fopen版本。另外一个方式是避免使用fopen,使用open和fdopen取代之。
有人给我提了这样个有用的建议关于大文件:
Hello.
I needed to essentially treat an entire hard drive as one large file, and discovered I couldn't get past 2 GB into it until I defined the following macros in my source:
#define _GNU_SOURCE #define _LARGEFILE_SOURCE #define _LARGEFILE64_SOURCE #define _FILE_OFFSET_BITS 64
The _LARGEFILE_SOURCE macro permits the use of fseeko() and ftello(), among other things. _LARGEFILE64_SOURCE actually permits the use of 64-bit functions like fseeko64() and ftello64() and, finally, _FILE_OFFSET_BITS determines which interface will be used by default. If the macro isn't defined or is defined as 32 then the default interface is 32 bits. If it is defined as 64 then the default interface is the 64-bit interface, and a call to fseeko() will use this 64 bit interface.
I cribbed from the file NOTES that accompanies glibc v2.2.5 on my Slackware 8.1 box to write this E-mail but I know these extensions exist in glibc v2.2.3 as well.
HTH
15.我怎样在一个进程里使用大于3GB的虚拟内存?
唯一真正解决你的问题的方法是使用64位的平台,但也有另外的变通方法。正常情况下4GB的空间被等分成4份,每个有着不同的作用。第一块是用于执行程序和brk/sbrk 分配。第二块是用于mmaps,共享库和malloc的mmaps,第三块用于栈空间,第四用于内核本身。由于堆的增长自下而上和栈的增长自上而下,一个未使用的部分可用于其他。您可能希望通过改变TASK_UNMAPPED_BASE定义在linux / include / asm的/ processor.h改变BRK和mmap之间的分裂。或者您也可以尝试Linux版本2.4.17此修补程序。该补丁还需要更多的测试。您还可以更改用户空间和内核空间之间的分割,是定义在linux / include / asm的/ page_offset.h PAGE_OFFSET_RAW。请注意,不正确的设置可以使内核无法使用。你应该留下至少8MB加#%3,需要蒸出4MB的物理空间为你的内核。
已建议在内核邮件列表(linux-kernel@vger.kernel.org)完全不同的方法。您可以分配使用的SysV或POSIX共享内存共享内存段。然后根据需要映射和取消映射段。 (这是有点类似于DOS下的EMM)单个段的规模有限,但你可以有很多段甚至超越4GB如果您有足够的物理RAM或交换空间的总大小。可以重映射文件或文件的一部分在你的进程中。内存映射文件在大多数情况下,如同共享内存。
16.为什么 Red Hat Linux 7.3, 8.0, and 9加载glibc 到42000000?
在Red Hat Linux 7.3, 8.0, and 9 中c库被加载到一个指定的地址而不是动态的地址。
On Red Hat Linux 7.3, 8.0, and 9 the C library is compiled for a fixed address rather than a dynamic address. The constant RedHat has chosen is a little above the default TASK_UNMAPPED_BASE, so even though a few memory mappings have been made before libc is mapped, it can get the address it wants. In most cases this fixed address for libc is a good idea, but it does have a single disadvantage. If you change TASK_UNMAPPED_BASE to get more contiguous address space, the choice of 42000000 is very bad. You can install the source rpm and change this address in the spec file to something else like 07000000, you should also add your initials to the release field so it is always clear that this is a version you changed. Then you can build a new rpm file and install it. On RedHat 7.3 an easier, but not as good solution is to install the i386 version of glibc. (Does this work on RH8.0 as well?)
17.在linux下我怎样使用posix的共享内存?How do I use posix shared memory in Linux
你可以在shm_open(3) man page上得到一些帮助。上面没有提到的是你需要包含头文件<sys/fcntl.h>.同时记得要在编译时加上-lrt选项。基本上linux实现shm_open 依赖于/dev/shm 通过调用open访问,同时文件/dev/shm必须存在同时是临时文件系统的挂载点(这个文件系统过去称为shm,现在这个名称废弃了)。注意在Fedora Core 1(i586上)使用-lrt编译的程序不会可靠地运行。
18.我怎样从终端上读取一个单个字符
‘Floyd Davidson 写了一个漂亮的 posting 在 comp.os.linux.development.apps上,有关于此的 .
僵尸进程是一个被父进程抛弃的死掉的子进程。如果你想生成一个子进程,但是又不想承担父进程的责任,你可以调用两次fork。简单的来说,如果你只想直到调用成功或失败,你可以这样做(未测试):
int doublefork() { pid_t pid=fork(); int status; switch(pid) { case 0: switch(fork()) { case 0: return 0; case -1: _exit(1); default: _exit(0); } case -1: return -1; } waitpid(pid,&status,0); if (status) return -1; return 1; }
复杂一点的情况来说,你需要直到新进程的id,或失败的错误码errno,进程间通讯(未测试):
pid_t doublefork() { struct { pid_t pid2; int errno_; } data; int pipefd[2]; pid_t pid1; if (pipe(pipefd)) return -1; pid1=fork(); switch(pid1) { case 0: close(pipefd[0]); data.pid2=fork(); data.errno_=errno; if (data.pid2) { write(pipefd[1],&data,sizeof(data)); _exit(0); } close(pipefd[1]); return 0; case -1: close(pipefd[0]); close(pipefd[1]); return -1; } close(pipefd[1]); assert(read(pipefd[0],&data,sizeof(data))==sizeof(data)); close(pipefd[0]); waitpid(pid1,NULL,0); errno=data.errno_; return data.pid2; }
一些系统可能有其他的处理方式,上面的方式可能是最常见的. 可以读取Unix Programming FAQ的相关章节this section ;
20. 我怎样等待一个非我子进程的进程终止?
最方便的莫过于由一个方式得到任意自己想要的进程终止的通知了,然,不幸的是,没有标准的方式来得到这些。
这有一些技巧可能有所帮助:
1.如果想要等待的进程和所在的进程有着亲缘关系,你可以使用管道的方式。管道的产生需要一个共同的祖先进程调用系统调用pipe()。()被等待的进程是写入者,等待的进程作为读取端,当读到EOF时,进程终止了。这个的关键点是写入端的进程关闭写管道。比起等待进程终止,管道还有更好的使用方法。
- 你可以等待一组内所有进程的终止,通过让他们作为管道的写入端。
- 多个进程可以等待一个进程终止,因为可以有多个读取者;
- You can make use of the close on exec flag on the write end of the pipe to indicate if you want to be notified if the process calls execve and not only if it terminates.
- You can even use select on the read end of the pipe.
2. 你可以建一个循环,循环中每隔半秒检查这个进程是否还存在。这不是一个好的方案,但是如果情况不允许你选择管道的方式,这也是一个适合的方案。
3.最后,你可以使用ptrace(),但这通常不是一个好主意,因为:
-
会影响进程的运行
-
是进程运行变慢。
-
一个进程只能包含一个跟踪者。
-
只能跟踪自己的进程。
21.我怎样探测一个进程是否存在?
你可以使用系统调用kill()。将信号代码部分填充未0,不会发送信号给指定进程,但是依然会进行错误检查。如果返回值为0,说明进程依然存在,你可以给它发信号。
如果返回-1,说明进程不存在或者是一个不能向它发送信号的进程,在这种情况下使用errno来找出到底是那种情况。如果是ESRCH 表明进程不存在,如果是EPERM 表明进程是存在的但是不允许给它发信号。查看kill(2) man page 可获取更多信息。
(网上有篇说到此点的文件:http://www.cnblogs.com/xuxm2007/archive/2011/04/15/2016735.html)
22.问什么进程接收到SIGSEGV但是没有产生core dump?
这有许多可能。我会将他们列出来,但是我有可能会遗漏一些。
core文件大小可能超出了当前的限制。查证是否有这个限制,然后取消它。sh或者类似的shell使用ulimit -a查看当前的限制,ulimit -S -c unlimited 样的指令关闭限制。最后确认它确实关闭了限制。在tsh csh或类似的shell中使用limit and limit coredumpsize unlimited.
你没有权限生成core文件。核实你是否有该目录的写权限,和如果存在一个core文件你有向其写的权限。注意core文件是位于生成core文件的进程的当前目录,它有可能和其父进程的当前目录不一样。
核实所在的文件系统是可写的且有足够的空闲空间。
如果工作目录下有一个与其同名的子目录,也会导致不生成core文件。
如果同名的core文件已经存在,同时它有着多个硬链接,那么系统也不会产生core文件。
核实执行程序的suid 或sgid 位是否使能,如果使能的话core默认下是关闭的。同样的情况是你有这文件的执行权限但是却没有该文件的读权限时。
一些内核版本在共享空间(AKA threads). 里不能使用core dump。最近的内核可以core dump,但是会文件名上注上进程的pid。
可能性文件的格式可能不是一个标准的格式不支持core dump。每一个可执行文件格式都必须由一个 core转存功能。
段错误可能导致内核的oops,查看系统日志是否有oops信息。
23.我怎样修改core文件名和所在位置?
在linux 2.4及以后版本中文件/proc/sys/kernel/core_uses_pid and /proc/sys/kernel/core_pattern 决定着core dump。如果core_uses_pid 被设置为1.每个core名称后会缀上进程的pid,默认的情况下是给多线程编程程序中。
你可以修改core_pattern从而改变更多的设置。你可以修改它从而改变core的文件名,你也可以指定一个文件路径从而不再当前目录生成core文件。这很方便
That can be handy because some file systems are not nice to core dump to. (The root file system, tmpfs, and nfs are examples of places where a core dump can hurt). You can use certain escape sequences in the name specification
%%
A % character
%p
The pid (if you use this, it will no longer be appended).
%u
The user id as a number
%g
The group id as a number
%s
The signal causing the dump
%t
The time when the dump started
%h
The hostname
%e
The name of the executable
举个例子,你可以设置路径为/mnt/bigfs/coredumps/%u/%e,这样不同的用户会将core文件放在不同的目录下,并以程序的名为名。使用这种方法你需要预先生成目录,比注意修改目录的所有者和适当的权限。如果你不想使用uid而想使用用户名访问文件,你可以自己建符号链接。
24. 我怎样从运行的进程中得到core?
如果你仅想让一个程序终止并产生一个core,你可以向它发送信号SIGABRT,可以使用kill从其他进程发送此信号。或者有进程本身调用kill, raise, or abort发送。如果你想得到一个core文件但不想让进程终止,事情就有点小麻烦。程序的初始化部分中,信号句柄中已经做了处理。从外部的话,内核没有提供一个简单的方法得到core文件而不杀死它。但gdb中提供了一个gcore命令实现了这一艰难的工作。在Fedora Core中你也可以在你的shell中使用gcore(它只是一个脚本调用gdb);
25.我怎样得到一个设置有suid的程序dump core?
如果你有源代码,你可以轻松的在main函数中加一条语句,使能保存core文件标志。
#include <sys/prctl.h> ... /* The last three arguments are just padding, because the * system call requires five arguments. */ prctl(PR_SET_DUMPABLE,1,42,42,42);
如果哪位兄弟知道一个简洁安全且不用重新编译代码的得到设置有suid标志的可执行程序的core文件,请告诉我。
如果你有源代码,你可以轻松的在main函数中加一条语句,使能保存core文件标志。同时我实现了一个模块如上所说你真的需要从设置有suid位的可执行程序得到core文件而不想重新编译程序,可以使用它。加载的时候传入想要core文件的进程pid,进程的保存core文件标志将被打开。卸载该模块任何时候都会返回一个出错值。(FIXME: Find out what the purpose of /proc/sys/kernel/core_setuid_ok is)
/proc下的文件不是真实的文件,它们是虚拟的文件。Kcore可以被用来调试运行中的内核。它的格式和通常的core文件类似。你不用担心,它一直存在并不会带来任何问题。如果你对其使用ls命令会显示文件的大小相当于系统内核存储空间的大小。但它不会占用任何磁盘空间和内存空间。(为什么不占用内存空间呢,memory翻译为内存对吗?);
27.我怎样找到一个用户的家目录?
针对不同的形式有着不同的方法。如果你仅是想知道当前用户的家目录,你可以使用环境变量HOME。在程序是使用getenv("HOME") 。你需要检查返回的指针是不是等于NULL和指向的字符串是否为空。如果环境变量HOME不恰当打印出错误信息并中止程序。在emacs和大多数shells中,当前用户目录也被称为“~”。这有一个代码示例:
char *myhome() { char *r=getenv("HOME"); if ((!r)||((*r) != '/')) { fprintf(stderr,"Fix the HOME environment variable.\n"); return "/"; } return r; }
寻找一个指定用户的主目录是另外一个问题。你需要使用函数getpwnam。传递给该函数一个用户名,它将返回一个结构体,你包含的其中的一个域中为用户的家目录。你需要检查函数的返回值,确认该用户是确实存在的。如果你得到一个空指针,你可以忽略掉返回的结构体。在emacs和大多数shell中,用户的主目录也被称为"~username". 下面是示例代码:
char *usershome(const char * name) { struct passwd *tmp=getpwnam(name); if (!tmp) return NULL; return tmp->pw_dir; }
比较少见的情况是一个设置有suid的程序需要知道当前用户家目录,这时你不能使用环境变量。通常情况下一个设置有suid位的程序不能使用任何环境变量。这种情况下你可以使用函数getpwuid。如下是示例代码:
char *myhome_suid_version() { struct passwdd *tmp=getpwuid(getuid()); if (!tmp) { fprintf(stderr,"You don't exist! Go away!\n"); exit(1); } return tmp->pw_dir; }
如果你不是在写一个设置有suid位的程序。你不用担心用户会修改家目录。事实上你应该接受用户会修改家目录。修改家目录这不是一个错误,不能接受修改家目录才是一个错误。
28. 我如何使用crypt生成和验证密码?
请注意,这仅介绍使用crypt,要真正使这个在Linux下使用,您还需要了解PAM如何工作。
crypt 接收两个字符串为参数,出口参数为一个指向字符串的指针。相同的函数使用两种不同的方式来生产密码和校验密码。第一个字符串参数是用户的密码类型。第二个字符串参数是salt。你可以传递一个字符串+一些其他东西给salt,crypt 直到salt有多长,如果太长它会取需要的那一部分。输出salt连接编码后的密码。输出的东西实际上被存放在密码文件里。
要校验密码,需要提供用户的密码类型,和秘密文件作为参数提供给crypt
Notice this only describes the use of crypt, to really make use of this under Linux you also need to understand how PAM works.
crypt takes two strings as arguments and outputs a pointer to one string. The same function can be used in two different ways to generate and verify a password. The first argument string is the password typed by the user. The second argument string is the salt. You are allowed to pass a string with something appended to the salt, crypt knows how long the salt is, and will only use the start of the salt string if it is too long. The output is the salt concatenated with the "encoded" password. This output string is what is actually stored in the password file.
To verify a password give the password typed by the user, and the entry from the password file as arguments to crypt. Compare the output against the entry from the password file. Because the salt is a prefix of the password file entry, crypt will find the salt in the string.
if (strcmp(crypt(typed_password, passwd_file_entry), passwd_file_entry)) { fprintf(stderr,"Invalid login\n"); exit(1); }
To generate an entry for a password file, your program must choose a random salt. Actually there are different formats for the salt depending on the type of encoding. Conventionally UNIX systems have used a variant of DES. That is not very secure, so Linux have another more secure version based on MD5, which is unfortunately less portable. Since crypt look on the salt to find out which type of password is being used, the above verification code works without modifications for both types of passwords. When generating the password you however have to make the decission as you generate a salt. The salt must contain some random chars. To generate random chars using /dev/random is the recomended approach. The random chars are taken from the set of upper and lower case letters, digits, period, and slash. The DES salt is just two random chars. The MD5 salt is dollar one dollar eight random chars dollar. Here is some example code.
#define _XOPEN_SOURCE #include <stdlib.h> #include <string.h> #include <stdio.h> #include <stdint.h> #include <unistd.h> #include <fcntl.h> char *encode(char *password) { int i=0; uint8_t rand_buf[8]; /* Changed to $6$ to use the more secure SHA-512 hash instead of MD5 */ char salt[13]="$6$________$"; int fd=open("/dev/random",O_RDONLY); if (fd==-1) { perror("/dev/random"); exit(1); } while(i<8) i+=read(fd,rand_buf,8-i); close(fd); for (i=0;i<8;++i) salt[i+3]="./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[rand_buf[i]&63]; return crypt(password,salt); }
Notice that crypt returns a pointer to a static buffer. That means you don't need to call free on the pointer returned by crypt. OTOH the string will be overwritten by the next crypt call. For that reason you should not expect crypt to be thread safe. A program using crypt need to be linked with –lcrypt
29. 我怎么知道系统内存的总量
在linux系统里,你可以使用函数sysconf(_SC_PHYS_PAGES)得到系统实际物理内存页数。该数字不包含保留的页数,但是它是你的应用程序实际可以使用的内存页数。你可以使用sysconf(_SC_PAGE_SIZE)查的每页的大小。注意你不能简单的将这两个数字相乘而得到系统的物理内存字节数,因为这个乘积有可能会溢出。空闲页的大小你可以调用函数sysconf(_SC_AVPHYS_PAGES)得到,使用这些数字的时候请当心(当心什么呢?);大多数程序不需要关系系统内存的大小,只需要想要多少就申请多少。系统会处理剩下的工作。不要忘记检查函数malloc的返回值。
一些算法需要知道多大的物理内存可以使执行起来最优。在这种情况下,注意你的程序不是系统中唯一在运行的程序。多留一些空间是个好主意,如果你是这分配更多的空间,这时候多余一点的未使用的空间比摧毁交换分区强。设置一个申请内存页的上限,比如在32位体系下最多为4GB的物理内存。一个单独的进程不应该申请大于此数的内存,因为地址线宽的问题,你需要使用以段代码想这样:
int allocpages=(sysconf(_SC_PHYS_PAGES)*3)>>2; if (allocpages>400000) allocpages=400000; allocation=malloc(allocpages*sysconf(_SC_PAGE_SIZE));
如果你有一个良好的缓存处理算法,你可以尽可能多的使用物理内存以获得良好的性能。
大多数的内核版本支持过量的内存使用,这意味这允许你使用大于可能的内存,当问题发生时会杀死进程。为了避免非预期的杀死进程,你要保证足够的交换空间。我的建议是交换分区是实际
物理内存的三倍大小。
30. 我怎样得到网络接口和它们的ip 物理地址的清单?
下面是来自Floyd Davidson的例子。在这之下有更简短的例子。
/* display info about network interfaces */ #define _BSD_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/ioctl.h> #include <sys/types.h> #include <net/if.h> #include <net/if_arp.h> #include <arpa/inet.h> #define inaddrr(x) (*(struct in_addr *) &ifr->x[sizeof sa.sin_port]) #define IFRSIZE ((int)(size * sizeof (struct ifreq))) static int get_addr(int sock, char * ifname, struct sockaddr * ifaddr) { struct ifreq *ifr; struct ifreq ifrr; struct sockaddr_in sa; ifr = &ifrr; ifrr.ifr_addr.sa_family = AF_INET; strncpy(ifrr.ifr_name, ifname, sizeof(ifrr.ifr_name)); if (ioctl(sock, SIOCGIFADDR, ifr) < 0) { printf("No %s interface.\n", ifname); return -1; } *ifaddr = ifrr.ifr_addr; printf("Address for %s: %s\n", ifname, inet_ntoa(inaddrr(ifr_addr.sa_data))); return 0; } int main(void) { unsigned char *u; int sockfd, size = 1; struct ifreq *ifr; struct ifconf ifc; struct sockaddr_in sa; if (0 > (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP))) { fprintf(stderr, "Cannot open socket.\n"); exit(EXIT_FAILURE); } ifc.ifc_len = IFRSIZE; ifc.ifc_req = NULL; do { ++size; /* realloc buffer size until no overflow occurs */ if (NULL == (ifc.ifc_req = realloc(ifc.ifc_req, IFRSIZE))) { fprintf(stderr, "Out of memory.\n"); exit(EXIT_FAILURE); } ifc.ifc_len = IFRSIZE; if (ioctl(sockfd, SIOCGIFCONF, &ifc)) { perror("ioctl SIOCFIFCONF"); exit(EXIT_FAILURE); } } while (IFRSIZE <= ifc.ifc_len); /* this is an alternate way to get info... */ { struct sockaddr ifa; get_addr(sockfd, "ppp0", &ifa); } ifr = ifc.ifc_req; for (;(char *) ifr < (char *) ifc.ifc_req + ifc.ifc_len; ++ifr) { if (ifr->ifr_addr.sa_data == (ifr+1)->ifr_addr.sa_data) { continue; /* duplicate, skip it */ } if (ioctl(sockfd, SIOCGIFFLAGS, ifr)) { continue; /* failed to get flags, skip it */ } printf("Interface: %s\n", ifr->ifr_name); printf("IP Address: %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data))); /* This won't work on HP-UX 10.20 as there's no SIOCGIFHWADDR ioctl. You'll need to use DLPI or the NETSTAT ioctl on /dev/lan0, etc (and you'll need to be root to use the NETSTAT ioctl. Also this is deprecated and doesn't work on 11.00). On Digital Unix you can use the SIOCRPHYSADDR ioctl according to an old utility I have. Also on SGI I think you need to use a raw socket, e.g. s = socket(PF_RAW, SOCK_RAW, RAWPROTO_SNOOP) Dave From: David Peter <dave.peter@eu.citrix.com> */ if (0 == ioctl(sockfd, SIOCGIFHWADDR, ifr)) { /* Select which hardware types to process. * * See list in system include file included from * /usr/include/net/if_arp.h (For example, on * Linux see file /usr/include/linux/if_arp.h to * get the list.) */ switch (ifr->ifr_hwaddr.sa_family) { default: printf("\n"); continue; case ARPHRD_NETROM: case ARPHRD_ETHER: case ARPHRD_PPP: case ARPHRD_EETHER: case ARPHRD_IEEE802: break; } u = (unsigned char *) &ifr->ifr_addr.sa_data; if (u[0] + u[1] + u[2] + u[3] + u[4] + u[5]) { printf("HW Address: %2.2x.%2.2x.%2.2x.%2.2x.%2.2x.%2.2x\n", u[0], u[1], u[2], u[3], u[4], u[5]); } } if (0 == ioctl(sockfd, SIOCGIFNETMASK, ifr) && strcmp("255.255.255.255", inet_ntoa(inaddrr(ifr_addr.sa_data)))) { printf("Netmask: %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data))); } if (ifr->ifr_flags & IFF_BROADCAST) { if (0 == ioctl(sockfd, SIOCGIFBRDADDR, ifr) && strcmp("0.0.0.0", inet_ntoa(inaddrr(ifr_addr.sa_data)))) { printf("Broadcast: %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data))); } } if (0 == ioctl(sockfd, SIOCGIFMTU, ifr)) { printf("MTU: %u\n", ifr->ifr_mtu); } if (0 == ioctl(sockfd, SIOCGIFMETRIC, ifr)) { printf("Metric: %u\n", ifr->ifr_metric); } printf("\n"); } close(sockfd); return EXIT_SUCCESS; }
This program written by Frank Becker does part of the job:
Frank Becker写的这个程序做了部分这样的工作:
#include <net/if.h> #include <sys/ioctl.h> #include <stdio.h> #include <stdlib.h> int main( int argc, char *argv[]) { char *ifname = "eth0"; struct ifreq ifr; int fd; int i; if( argc==2 ) ifname = argv[1]; fd = socket(AF_INET,SOCK_DGRAM, 0); if (fd >= 0) { strcpy(ifr.ifr_name, ifname); if (ioctl(fd, SIOCGIFADDR, &ifr) == 0) { printf( "%s : ", ifname); for( i=2; i<6; i++) { unsigned char val = (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[i]; printf( "%d%s", val, i==5?" ":"."); } printf( "\n"); } } return 0; }
Another program written by Gary Desrosiers will print the MAC address of an interface:
另外一个由Gary Desrosiers写的程序实现了打印物理地址的接口:
#include <string.h> #include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <net/if.h> #include <sys/types.h> #include <arpa/inet.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <netdb.h> int main(int argc,char *argv[]) { int r; struct protoent *proto; int sock; struct ifreq ifr; if(argc < 2) { printf("Usage: dumpmac ifname\n"); exit(1); } proto = getprotobyname("tcp"); sock = socket(PF_INET,SOCK_STREAM,proto->p_proto); memset(&ifr,0,sizeof(struct ifreq)); strcpy(ifr.ifr_name,argv[1]); r = ioctl(sock,SIOCGIFHWADDR,&ifr); if(r != 0) { printf("Couldn't get mac address\n"); exit(1); } printf("%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X\n", (unsigned char)ifr.ifr_hwaddr.sa_data[0], (unsigned char)ifr.ifr_hwaddr.sa_data[1], (unsigned char)ifr.ifr_hwaddr.sa_data[2], (unsigned char)ifr.ifr_hwaddr.sa_data[3], (unsigned char)ifr.ifr_hwaddr.sa_data[4], (unsigned char)ifr.ifr_hwaddr.sa_data[5]); return(0); }
31.我怎样可以将我的调试输出到一个新的调试终端上
一个例子可能会有所帮助:debugxterm.c. 更好的一个解决办法是使用xterm的-S选项,查看xterm的man page获得更多的信息。
32.我打算写一个文件系统,我应该从哪里开始呢?
写一个文件系统你学要学两件事情:你打算使用的数据结构,linux内核的API接口。
首先你先去看一看目前存在的最简单的文件系统的源代码。linux下最简单的文件系统是ramfs.有两种文件系统,一种是使用块设备另一种是不使用块设备。Ramfs 是那种不
使用块设备的,与之相同的还有大多数网络文件系统和像proc的虚拟的文件系统。
通常你在磁盘上存储东西的文件系统是另种使用块设备的文件系统
- The filesystems you actually store on disks are those using blockdevices, they can be divided into two groups. The simplest are those that can be implemented using a get_block/bmap function, this includes ext2, minix, and fat. The simplest from this group and yet fully posix compliant is minix. If the data on disk is stored in some more compact way, the simple solution will not be possible. At this point it obviously start getting a little complicated, there does however still exist a quite simple readonly filesystem which has it's own readpage implementation, that is romfs.
Before implementing the filesystem as a kernel module, you should get a feeling with the datastructures you will be using. If you are using a blockdevice, you should write usermode tools to manipulate the datastructures on the disk. You are going to need these pieces of code anyway, because you will eventually need three tools for your filesystem: mkfs, fsck, and debugfs. Once you have working usermode code for the basic tasks, you can start doing it in the kernel. If you are writing another kind of filesystem, there is still a few things to do before writing kernel code. If you are writing a networking filesystem, you want to know the protocols you are going to use, and you want to test them in usermode first.
Does my kernel leak memory?
If you didn't modify the kernel, it probably doesn't leak memory. Of course eventually kernels do get released with a bug that could cause a leak. Before you conclude that you have found a bug, you will need to know how to find and interpret informations about the memory usage. The kernel will attempt to use most of the otherwise free memory for caching disk conents. This means that there will normally be very litle memory, that is actually free. Take a look on this output from the free command:
total used free shared buffers cached Mem: 448980 443196 5784 0 36604 303404 -/+ buffers/cache: 103188 345792 Swap: 2096440 0 2096440
The first line tells me, that only 5784KB of my 448MB of memory are free. But in the second line where the 332MB of memory used for buffer and cache memory is substracted from the used memory I see, that only 100MB of the memory is used for other allocations. Some of those 100MB will be used for slabs. You can use slabtop to see information about this memory usage. Another possibility is to use my script which will list slab allocations(requires kernel version 2.4). Look on a few selected lines from the output:
inode_cache 4863 pages 19452 KB dentry_cache 1231 pages 4924 KB buffer_head 1476 pages 5904 KB ---------------------------------------- Total 8554 pages 34216 KB ----------------------------------------
Here we see, that most of the 33MB used for slabs is actually also cached disk contents and management for the buffers. This is quite normal, and those slabs will get freed if memory gets tight. The rest of the memory in my system is mostly used by applications. Even if we sum up all the allocations I have talked about, there will still be a few KB of memory that is not accounted for. That is also normal, there are different types of allocations that use get_free_pages() directly. Those pages are only listed as used, and does not figure anywhere else, but that doesn't mean they have leaked. Also notice that there is a difference between the 448MB I have and the 438MB free shows. That is because the mem_map will use 1.7% of my physical memory, and the kernel image itself also use a few MB. Those are not part of the total memory as reported by the kernel.
So now that you know a litle about the many different ways memory is allocated under Linux, how do you tell, if there is a leak? First of all if you suspect some action causes a leak, you should repeat it over and over again. If memory is allocated only the first time, it is probably not a leak. If there really is a leak, it will allocate more memory each time. Look on the slabs, if one type of slabs keep growing, you might have found a leak. Ignore the three slabs I told you about earlier. If the leaked memory is not allocated through slabs, it must be allocated at least one page at a time, in which case it will quickly grow large enough to be easilly noticed. Eventually the system will die when it really runs out of memory.
In addition to just monitoring the memory usage, you can also try to force the system to free memory. Let a program allocate a lot of virtual memory and touch each page. That will force the kernel to free anything that can be freed. An easy way to do this is by letting tail read from /dev/zero.
tail -c400m /dev/zero
This will allocate 400MB and access it over and over again.
英文原文:http://www.kasperd.net/~kasperd/comp.os.linux.development.faq#unresolved