LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 870|回复: 0

有关内核的边缘性问题(我的个人解答,还往高手指点)

[复制链接]
发表于 2005-3-15 13:27:37 | 显示全部楼层 |阅读模式
1.        在i386中应用程序运行在什么保护级别?
在实模式下,没有级别之分。在保护模式下,CPU有4个特权级别。特权级0,特权级1,特权级2,特权级3。Linux只使用了0和3,分别称为内核态和用户态。在内核态下,CPU可执行任何指令,在用户态下CPU只能执行非特权指令。因此在i386中应用程序运行在特权级3下,也就是用户态下,执行的是非特权指令。
2在i386体系结构中,每个程序可以使用的页目录中最多含有多少个目录项?每个页表最多含有多少个页表项?页表项所指向的内存页叫做什么?
在i386体系结构中,每个程序可以使用的页目录中最多有1024个页目录项;每个页表最多含有1024个页表项;页表项所指向的内存页叫做页帧号。
3 如果init进程创建的一个子进程退出了,init进程为执行同样的功能,可以按需要再创建一个新的子进程,但这要求在一个文件中事先定义才行,这个文件叫什么名字?在什么目录下?
init程序使用/etc/inittab作为脚本文件来创建系统中的新进程。
4 进程的主要特征是什么?
一个进程是具有独立地址空间的运行单位,它也是Linux基本调度单位。对于一个正在运行的程序来说,一个进程由如下元素组成:
A.        程序的当前上下文,它是程序当前执行的状态
B.        程序的当前执行目录
C.        程序访问的文件和目录
D.        程序的信任状态或者说访问权限,比如它的文件模式和所有权
E.        内存和其他分配给进程的系统资源
5 所谓的0号进程来自那里?在系统启动正常运行后,0号进程在什么情况下才被调度执行?
系统最初的引导进程(init_task)在引导结束后即成为cpu 0上的idle进程。在每个cpu上都有一个idle进程,这些进程登记在init_tasks[]数组中,并可用idle_task()宏访问。idle进程不进入就绪队列,系统稳定后,仅当就绪队列为空的时候idle进程才会被调度到。
init_task的task_struct是静态配置的,定义在[include/linux/sched.h]中的INIT_TASK()宏中,其中与调度相关的几个属性分别是:
state:TASK_RUNNING;
counter:10*HZ/100;i386上大约100ms
nice:0;缺省的优先级
policy:SCHED_OTHER;非实时进程
cpus_runnable:-1;全1,未在任何cpu上运行
cpus_allowed:-1;全1,可在任何cpu上运行
在smp_init()中(实际上是在[arch/i386/kernel/smpboot.c]中的smp_boot_cpus()中),init_task的processor属性被设为0,对应的schedule_data也设置好相应的值。在创建了一个核心线程用于执行init()函数之后([/init/main.c]rest_init()),init_task设置自己的need_resched等于1,然后调用cpu_idle()进入IDLE循环。
6什么是僵尸进程?内核怎样处理僵尸进程?
在Linux进程的5种状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。
从编程角度来说,僵尸进程是在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装SIGCHLD信号处理函数调用waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为僵尸进程,无法正常结束,此时即使是root身份kill -9也不能杀死僵尸进程。
一个孤儿进程是一个父进程在调用wait()或waitpid()之前就已经退出得子进程。因此,补救办法是杀死僵尸进程的父进程(僵尸进程的父进程必然存在),使僵尸进程成为“孤儿进程”,过继给1号进程init,init进程成为子进程的父进程并且收集它的退出状态,从而避免僵尸进程。
7请编写一段C语言代码,使之创建一个进程扇。如题14图所示。

<img src=http://www.linuxsir.cn/bbs/attachment.php?attachmentid=22839&stc=1></img>
由于我不会发图片,如果看不到图的话,请查阅附件。

相关代码如下,SuSE92下编译通过,结果ok,如果不清楚请查阅附件。

#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>

int main(void) {

        pid_t parents,child_1,child_2,child_3,child_4;
        int status;

        /* This is the first fork division */
        if((parents = fork()) < 0) {
                perror("parents -> child_1 fork.\n");
                exit(EXIT_FAILURE);
        } else if(parents == 0) {
                child_1 = getpid();
                printf("i am child_1, pid = %d\n",getpid());
                exit(EXIT_SUCCESS);
        } else {
                waitpid(child_1,&status,0);  // wait for child_1
                printf("parents -> child_1 done.\n");

                /* This is the second fork division */
                if((parents = fork()) < 0) {
                        perror("parents -> child_2 fork.\n");
                        exit(EXIT_FAILURE);
                } else if (parents == 0) {
                        child_2 = getpid();
                        printf("i am child_2, pid = %d\n",getpid());
                        exit(EXIT_SUCCESS);
                } else {
                        waitpid(child_2,&status,0);  // wait for child_2
                        printf("parents -> child_2 done.\n");

                        /* This is the third fork division */
                        if((parents = fork()) < 0) {
                                perror("parents -> child_3 fork.\n");
                                exit(EXIT_FAILURE);
                        } else if (parents == 0) {
                                child_3 = getpid();
                                printf("i am child_3, pid = %d\n",getpid());
                                exit(EXIT_SUCCESS);
                        } else {
                                waitpid(child_3,&status,0);  // wait for child_3
                                printf("parents -> child_3 done.\n");

                                /* This is the fourth fork division */
                                if((parents = fork()) < 0) {
                                        perror("parents->child_4 fork.\n");
                                        exit(EXIT_FAILURE);
                                } else if (parents == 0) {
                                        child_4 = getpid();
                                        printf("i am child_4, pid = %d\n",getpid());
                                        exit(EXIT_SUCCESS);
                                } else {
                                        waitpid(child_4,&status,0);  // wait for child_4
                                        printf("parents -> child_4x1 done.\n");
                                }
                        }
                }
                printf("i am parents. pid = %d.\n",getpid());
        }

        exit(EXIT_SUCCESS);
}


8你认为在用户空间编写的守护进程与内核态的守护进程有什么相同之处和不同之处?后台进程都属于守护进程吗?
用户空间编写的守护进程与内核态的守护进程的相同之处在于:在后台执行, 没有控制终端或登录 Shell 的进程。
不同点在于:用户编写的守护进程主要是针对用户进程的服务工作;而内核态的守护进程其工作主要针对于系统自身的服务需要。
后台进程并不是都属于守护进程,因为简单地在后台启动一个程序并非足够是这些长时间运行的程序,那种方法没有正确地将进程从启动它的终端脱离(detach)。而且,启动守护程序的普遍接受的方法是简单地手工执行或从rc脚本程序执行(译者注:rc:runcom);并希望这个守护程序将其自身安置到后台。
9 你认为操作系统执行并发进程的最关键需求是什么?
充分利用CPU资源,当一个进程在等待数据时(从网络,外部设备等),其它进程可占用CPU。
10 下面是用C语言实现的一个“忙等待”操作的代码段:

int tas(int *lock)
{
       int retval;
retval=*lock;
*lock=1;
return retval;
}
             题7.4图
如果将*lock的初值设置为1,那么下列语句的执行结果是什么?
while(tas(&lock))
        因为tas(&lock)的返回值为1,所以执行结果是无限循环,无限等待。
       
下列代码是按照TestAndSet来实现的信号量,试分析它是否会引起死锁?

void down(semaphore_t *s){         void up(semaphore_t *s){
    for(;; ){                            while(tas(&lock))
       while(tas(&lock))                    ;
         ;                                 *(s)++;
       if(*s>0){                          lock = 0;
         *(s)--;                   }
         break;
       }
       lock = 0;
    }
    lock = 0;
}
题10图
程序中的函数tas(&lock)就是第10题给出的tas(&lock)函数。
11 进程怎样确定一个时间片时值的大小?是越长越好呢?还是越短越好?
2.4内核中进程缺省时间片是根据以下公式计算的:
/* 节选自[kernel/sched.c] */#if HZ < 200#define TICK_SCALE(x)                ((x) >> 2)#elif HZ < 400#define TICK_SCALE(x)                ((x) >> 1)#elif HZ < 800#define TICK_SCALE(x)                (x)#elif HZ < 1600#define TICK_SCALE(x)                ((x) << 1)#else#define TICK_SCALE(x)                ((x) << 2)#endif#define NICE_TO_TICKS(nice)        (TICK_SCALE(20-(nice))+1)……schedule(){……p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice);……}
如上所述,时钟中断将不断对当前运行的非IDLE进程进行时间片剩余值减1的操作,直至所有就绪队列中的counter都减为0了,就在schedule()中对每个进程(包括休眠进程)利用上述公式执行时间片的更新。其中在[include/asm-i386/param.h]中定义了HZ为100,而counter通常初值为0,nice缺省为0(nice在-20到19之间选择),所以,i386下counter的缺省值为6,也就是大约60ms(时钟中断大约每10ms一次)。
同时,对于休眠的进程而言,其参与计算的counter非0,因此实际上它的counter是在累加,构成一个等比数列COUNTER=COUNTER/2+k,1<k<=11,其最大值趋近于2*k,也就是说,2.4系统中进程的时间片不会超过230ms。
时间片的长度对系统性能影响也很大。如果太短,进程切换就会过于频繁,开销很大;如果太长,系统响应就会太慢,Linux的策略是在系统响应不至于太慢的前提下让时间片尽可能地长。
12 操作系统设置系统调用的目的是什么?如何实现在内核中添加新的系统调用?
操作系统设置系统调用的目的主要有三个:
A、        安全,用户模式运行的程序不能自主地访问某些敏感的内核变量。
B、        效率,如果没有系统调用,每一个应用程序就将直接面对系统硬件,那么如果程序想要运行就要自己用底层硬件代码写起,不仅枯燥而且容易出错。
C、        为用户程序提供强大的系统支持
13 什么是重定位?存储器管理为什么需要重定位技术?
为了使每个进程都有起独立的地址空间,存储器管理需要重定位技术。重定位的实质是地址变换,它将进程地址空间中的逻辑地址转换为主存空间中的物理地址,从而保证作业能够正常执行。
静态重定位:在程序装入内存时,一次性完成地址映射,一般在连接装配时由软件完成。
动态重定位:在程序运行过程中,完成地址映射,既在逐条指令执行时完成,一般为了提高效率,由硬件支持,软硬件结合完成。
14假定进程以读方式打开一个文件后,再执行fork,父进程和子进程将都可以读这个文件。这两个进程的读操作和写操作各有何关系?
父进程和子进程的读操作是一种叠加访问关系。例如有文件file.txt其内容为0123456789,某进程p打开该文件,此时文件指针定为在0处,然后fork(),父子进程的文件指针都定位于0处。由于父子进程的执行顺序未知,所以我们假设现在子进程占用CPU的时间片并开始从0处读取文件,当时间片结束时子进程的文件指针定位到2,而父进程占用下一个CPU的时间片,由于父子进程的在fork()执行之后的地址空间完全相同,所以此时的父进程并不会原来的0处开始对文件进行读写,而是从上一个CPU时间片结束时子进程的文件指针定位处开始读写取文件,即从2处开始读写文件。若父进程执行的是读操作的话那么它会从2处向后读出文件内容;若父进程执行的是写操作的话那么它会从2处开始向文件覆盖写入内容。
15为什么用户在第一次访问任何文件之前,都必须先调用open()系统调用来打开指定的文件,然后才能对该文件执行读、写以及修改操作,在对文件操作结束必须关闭文件。
用户第一次访问任何文件之前都必须要打开该文件,其主要原因是在内存开辟一个缓冲区为需要访问文件的进程所使用,当执行读文件的操作时,从磁盘文件将数据先读入内存缓冲区,装满后再从内存缓冲区依此读入接收的变量。执行写文件的操作时,先将数据写入内存缓冲区,待内存缓冲区装满后再写入文件。由此可以看出,内存缓冲区的大小,影响着实际操作外存的次数,内存缓冲区越大,则操作外存的次数就少,执行速度就快、效率高。
Linux内核为每一个进程所维护该进程打开的文件建立一张记录表,而通过一个索引值来对这张记录表进行访问,而这个索引值就是所谓的文件描述符。之所以必须先调用open()系统调用来打开指定文件,主要原因就是只有open()系统调用成功后才能返回一个文件描述符。
16 将设备和普通文件统一命名的好处是什么?
这是一个核心的Linux I/O概念——文件抽象。在Linux上,几乎每一样东西都是一个文件,至少抽象的看是这样。这一事实也是Linux最具独创性的设计特色之一,因为它让大量的资源,比如内存、磁盘空间、进程间的通信通道、网络通信通道、磁带驱动器、控制台、串口、伪终端、打印端口、声卡、鼠标甚至其他运行的进程等等具有了统一的编程接口。
17        在进程处理第一个收到的信号之前,又收到了若干个同样信号将怎样处理?对这种情况还会有其他更好的处理方法吗?
A.“不可靠信号”
Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做"不可靠信号",信号值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是:
进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。
信号可能丢失,后面将对此详细阐述。因此,早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。
Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失。
B、“可靠信号”
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种Unix版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。POSIX.4对可靠信号机制做了标准化。但是,POSIX只对可靠信号机制应具有的功能以及信号机制的对外接口做了标准化,对信号机制的实现没有作具体的规定。
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
注:不要有这样的误解:由sigqueue()发送、sigaction安装的信号就是可靠的。事实上,可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
C、实时信号与非实时信号
早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),将来可能进一步增加,这需要得到内核的支持。前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做“可靠信号”。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册)。
当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册,则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做“不可靠信号”。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构(一个非实时信号诞生后,(1)、如果发现相同的信号已经在目标结构中注册,则不再注册,对于进程来说,相当于不知道本次信号发生,信号丢失;(2)、如果进程的未决信号中没有相同信号,则在进程中注册自己)。

相关资料来源于:IBM developWorks

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表