
1,SCHED_OTHER 分时调度策略,
2,SCHED_FIFO实时调度策略,先到先服务
3,SCHED_RR实时调度策略,时间片轮转
实时进程将得到优先调用,实时进程根据实时优先级决定调度权值,分时进程则通过nice和counter值决定权值,nice越小,counter越大,被调度的概率越大,也就是曾经使用了cpu最少的进程将会得到优先调度。
SHCED_RR和SCHED_FIFO的不同:
当采用SHCED_RR策略的进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。
SCHED_FIFO一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃。
如果有相同优先级的实时进程(根据优先级计算的调度权值是一样的)已经准备好,FIFO时必须等待该进程主动放弃后才可以运行这个优先级相同的任务。而RR可以让每个任务都执行一段时间。
相同点:
RR和FIFO都只用于实时任务。
创建时优先级大于0(1-99)。
按照可抢占优先级调度算法进行。
就绪态的实时任务立即抢占非实时任务。
所有任务都采用linux分时调度策略时。
1,创建任务指定采用分时调度策略,并指定优先级nice值(-20~19)。
2,将根据每个任务的nice值确定在cpu上的执行时间(counter)。
3,如果没有等待资源,则将该任务加入到就绪队列中。
4,调度程序遍历就绪队列中的任务,通过对每个任务动态优先级的计算(counter+20-nice)结果,选择计算结果最大的一个去运行,当这个时间片用完后(counter减至0)或者主动放弃cpu时,该任务将被放在就绪队列末尾(时间片用完)或等待队列(因等待资源而放弃cpu)中。
5,此时调度程序重复上面计算过程,转到第4步。
6,当调度程序发现所有就绪任务计算所得的权值都为不大于0时,重复第2步。
所有任务都采用FIFO时,
1,创建进程时指定采用FIFO,并设置实时优先级rt_priority(1-99)。
2,如果没有等待资源,则将该任务加入到就绪队列中。
3,调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务使用cpu,该FIFO任务将一直占有cpu直到有优先级更高的任务就绪(即使优先级相同也不行)或者主动放弃(等待资源)。
4,调度程序发现有优先级更高的任务到达(高优先级任务可能被中断或定时器任务唤醒,再或被当前运行的任务唤醒,等等),则调度程序立即在当前任务堆栈中保存当前cpu寄存器的所有数据,重新从高优先级任务的堆栈中加载寄存器数据到cpu,此时高优先级的任务开始运行。重复第3步。
5,如果当前任务因等待资源而主动放弃cpu使用权,则该任务将从就绪队列中删除,加入等待队列,此时重复第3步。
所有任务都采用RR调度策略时
1,创建任务时指定调度参数为RR,并设置任务的实时优先级和nice值(nice值将会转换为该任务的时间片的长度)。
2,如果没有等待资源,则将该任务加入到就绪队列中。
3,调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务使用cpu。
4,如果就绪队列中的RR任务时间片为0,则会根据nice值设置该任务的时间片,同时将该任务放入就绪队列的末尾。重复步骤3。
5,当前任务由于等待资源而主动退出cpu,则其加入等待队列中。重复步骤3。
系统中既有分时调度,又有时间片轮转调度和先进先出调度
1,RR调度和FIFO调度的进程属于实时进程,以分时调度的进程是非实时进程。
2,当实时进程准备就绪后,如果当前cpu正在运行非实时进程,则实时进程立即抢占非实时进程。
3,RR进程和FIFO进程都采用实时优先级做为调度的权值标准,RR是FIFO的一个延伸。FIFO时,如果两个进程的优先级一样,则这两个优先级一样的进程具体执行哪一个是由其在队列中的未知决定的,这样导致一些不公正性(优先级是一样的,为什么要让你一直运行?),如果将两个优先级一样的任务的调度策略都设为RR,则保证了这两个任务可以循环执行,保证了公平。 调度程序运行时,要在所有处于可运行状态的进程之中选择最值得运行的进程投入运行。选择进程的依据是什么呢?在每个进程的task_struct 结构中有这么四项:
policy, priority , counter, rt_priority
这四项就是调度程序选择进程的依据.其中,policy是进程的调度策略,用来区分两种进程-实时和普通;priority是进程(实时和普通)的优先级;counter 是进程剩余的时间片,它的大小完全由priority决定;rt_priority是实时优先级,这是实时进程所特有的,用于实时进程间的选择。
首先,Linux 根据policy从整体上区分实时进程和普通进程,因为实时进程和普通进程度调度是不同的,它们两者之间,实时进程应该先于普通进程而运行,然后,对于同一类型的不同进程,采用不同的标准来选择进程:
对于普通进程,Linux采用动态优先调度,选择进程的依据就是进程counter的大小。进程创建时,优先级priority被赋一个初值,一般为0~70之间的数字,这个数字同时也是计数器counter的初值,就是说进程创建时两者是相等的。字面上看,priority是“优先级”、counter是“计数器”的意思,然而实际上,它们表达的是同一个意思-进程的“时间片”。Priority代表分配给该进程的时间片,counter表示该进程剩余的时间片。在进程运行过程中,counter不断减少,而priority保持不变,以便在counter变为0的时候(该进程用完了所分配的时间片)对counter重新赋值。当一个普通进程的时间片用完以后,并不马上用priority对counter进行赋值,只有所有处于可运行状态的普通进程的时间片(p->;;counter==0)都用完了以后,才用priority对counter重新赋值,这个普通进程才有了再次被调度的机会。这说明,普通进程运行过程中,counter的减小给了其它进程得以运行的机会,直至counter减为0时才完全放弃对CPU的使用,这就相对于优先级在动态变化,所以称之为动态优先调度。至于时间片这个概念,和其他不同 *** 作系统一样的,Linux的时间单位也是“时钟滴答”,只是不同 *** 作系统对一个时钟滴答的定义不同而已(Linux为10ms)。进程的时间片就是指多少个时钟滴答,比如,若priority为20,则分配给该进程的时间片就为20个时钟滴答,也就是20*10ms=200ms。Linux中某个进程的调度策略(policy)、优先级(priority)等可以作为参数由用户自己决定,具有相当的灵活性。内核创建新进程时分配给进程的时间片缺省为200ms(更准确的,应为210ms),用户可以通过系统调用改变它。
对于实时进程,Linux采用了两种调度策略,即FIFO(先来先服务调度)和RR(时间片轮转调度)。因为实时进程具有一定程度的紧迫性,所以衡量一个实时进程是否应该运行,Linux采用了一个比较固定的标准。实时进程的counter只是用来表示该进程的剩余时间片,并不作为衡量它是否值得运行的标准,这和普通进程是有区别的。上面已经看到,每个进程有两个优先级,实时优先级就是用来衡量实时进程是否值得运行的。
这一切看来比较麻烦,但实际上Linux中的实现相当简单。Linux用函数goodness()来衡量一个处于可运行状态的进程值得运行的程度。该函数综合了上面提到的各个方面,给每个处于可运行状态的进程赋予一个权值(weight),调度程序以这个权值作为选择进程的唯一依据。
Linux根据policy的值将进程总体上分为实时进程和普通进程,提供了三种调度算法:一种传统的Unix调度程序和两个由POSIX.1b(原名为POSIX.4) *** 作系统标准所规定的“实时”调度程序。但这种实时只是软实时,不满足诸如中断等待时间等硬实时要求,只是保证了当实时进程需要时一定只把CPU分配给实时进程。
非实时进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时进程又增加了第三种优先级,实时优先级。优先级是一些简单的整数,为了决定应该允许哪一个进程使用CPU的资源,用优先级代表相对权值-优先级越高,它得到CPU时间的机会也就越大。
? 静态优先级(priority)-不随时间而改变,只能由用户进行修改。它指明了在被迫和其他进程竞争CPU之前,该进程所应该被允许的时间片的最大值(但很可能的,在该时间片耗尽之前,进程就被迫交出了CPU)。
? 动态优先级(counter)-只要进程拥有CPU,它就随着时间不断减小;当它小于0时,标记进程重新调度。它指明了在这个时间片中所剩余的时间量。
? 实时优先级(rt_priority)-指明这个进程自动把CPU交给哪一个其他进程;较高权值的进程总是优先于较低权值的进程。如果一个进程不是实时进程,其优先级就是0,所以实时进程总是优先于非实时进程的(但实际上,实时进程也会主动放弃CPU)。
当policy分别为以下值时:
1) SCHED_OTHER:这是普通的用户进程,进程的缺省类型,采用动态优先调度策略,选择进程的依据主要是根据进程goodness值的大小。这种进程在运行时,可以被高goodness值的进程抢先。
2) SCHED_FIFO:这是一种实时进程,遵守POSIX1.b标准的FIFO(先入先出)调度规则。它会一直运行,直到有一个进程因I/O阻塞,或者主动释放CPU,或者是CPU被另一个具有更高rt_priority的实时进程抢先。在Linux实现中,SCHED_FIFO进程仍然拥有时间片-只有当时间片用完时它们才被迫释放CPU。因此,如同POSIX1.b一样,这样的进程就象没有时间片(不是采用分时)一样运行。Linux中进程仍然保持对其时间片的记录(不修改counter)主要是为了实现的方便,同时避免在调度代码的关键路径上出现条件判断语句 if (!(current->;;policy&;;SCHED_FIFO)){...}-要知道,其他大量非FIFO进程都需要记录时间片,这种多余的检测只会浪费CPU资源。(一种优化措施,不该将执行时间占10%的代码的运行时间减少到50%;而是将执行时间占90%的代码的运行时间减少到95%。0.9+0.1*0.5=0.95>;;0.1+0.9*0.9=0.91)
3) SCHED_RR:这也是一种实时进程,遵守POSIX1.b标准的RR(循环round-robin)调度规则。除了时间片有些不同外,这种策略与SCHED_FIFO类似。当SCHED_RR进程的时间片用完后,就被放到SCHED_FIFO和SCHED_RR队列的末尾。
只要系统中有一个实时进程在运行,则任何SCHED_OTHER进程都不能在任何CPU运行。每个实时进程有一个rt_priority,因此,可以按照rt_priority在所有SCHED_RR进程之间分配CPU。其作用与SCHED_OTHER进程的priority作用一样。只有root用户能够用系统调用sched_setscheduler,来改变当前进程的类型(sys_nice,sys_setpriority)。
此外,内核还定义了SCHED_YIELD,这并不是一种调度策略,而是截取调度策略的一个附加位。如同前面说明的一样,如果有其他进程需要CPU,它就提示调度程序释放CPU。特别要注意的就是这甚至会引起实时进程把CPU释放给非实时进程。 真正执行调度的函数是schedule(void),它选择一个最合适的进程执行,并且真正进行上下文切换,使得选中的进程得以执行。而reschedule_idle(struct task_struct *p)的作用是为进程选择一个合适的CPU来执行,如果它选中了某个CPU,则将该CPU上当前运行进程的need_resched标志置为1,然后向它发出一个重新调度的处理机间中断,使得选中的CPU能够在中断处理返回时执行schedule函数,真正调度进程p在CPU上执行。在schedule()和reschedule_idle()中调用了goodness()函数。goodness()函数用来衡量一个处于可运行状态的进程值得运行的程度。此外,在schedule()函数中还调用了schedule_tail()函数;在reschedule_idle()函数中还调用了reschedule_idle_slow()。这些函数的实现对理解SMP的调度非常重要,下面一一分析这些函数。先给出每个函数的主要流程图,然后给出源代码,并加注释。
goodness()函数分析
goodness()函数计算一个处于可运行状态的进程值得运行的程度。一个任务的goodness是以下因素的函数:正在运行的任务、想要运行的任务、当前的CPU。goodness返回下面两类值中的一个:1000以下或者1000以上。1000或者1000以上的值只能赋给“实时”进程,从0到999的值只能赋给普通进程。实际上,在单处理器情况下,普通进程的goodness值只使用这个范围底部的一部分,从0到41。在SMP情况下,SMP模式会优先照顾等待同一个处理器的进程。不过,不管是UP还是SMP,实时进程的goodness值的范围是从1001到1099。
goodness()函数其实是不会返回-1000的,也不会返回其他负值。由于idle进程的counter值为负,所以如果使用idle进程作为参数调用goodness,就会返回负值,但这是不会发生的。
goodness()是个简单的函数,但是它是linux调度程序不可缺少的部分。运行队列中的每个进程每次执行schedule时都要调度它,因此它的执行速度必须很快。
//在/kernel/sched.c中
static inline int goodness(struct task_struct * p, int this_cpu, struct mm_struct *this_mm)
{ int weight;
if (p->;;policy != SCHED_OTHER) {/*如果是实时进程,则*/
weight = 1000 + p->;;rt_priority;
goto out;
}
/* 将counter的值赋给weight,这就给了进程一个大概的权值,counter中的值表示进程在一个时间片内,剩下要运行的时间.*/
weight = p->;;counter;
if (!weight) /* weight==0,表示该进程的时间片已经用完,则直接转到标号out*/
goto out;
#ifdef __SMP__
/*在SMP情况下,如果进程将要运行的CPU与进程上次运行的CPU是一样的,则最有利,因此,假如进程上次运行的CPU与当前CPU一致的话,权值加上PROC_CHANGE_PENALTY,这个宏定义为20。*/
if (p->;;processor == this_cpu)
weight += PROC_CHANGE_PENALTY;
#endif
if (p->;;mm == this_mm) /*进程p与当前运行进程,是同一个进程的不同线程,或者是共享地址空间的不同进程,优先选择,权值加1*/
weight += 1;
weight += p->;;priority; /* 权值加上进程的优先级*/
out:
return weight; /* 返回值作为进程调度的唯一依据,谁的权值大,就调度谁运行*/
}
schedule()函数分析
schedule()函数的作用是,选择一个合适的进程在CPU上执行,它仅仅根据'goodness'来工作。对于SMP情况,除了计算每个进程的加权平均运行时间外,其他与SMP相关的部分主要由goodness()函数来体现。
流程:
①将prev和next设置为schedule最感兴趣的两个进程:其中一个是在调用schedule时正在运行的进程(prev),另外一个应该是接着就给予CPU的进程(next)。注意:prev和next可能是相同的-schedule可以重新调度已经获得cpu的进程.
②中断处理程序运行“下半部分”.
③内核实时系统部分的实现,循环调度程序(SCHED_RR)通过移动“耗尽的”RR进程-已经用完其时间片的进程-到队列末尾,这样具有相同优先级的其他RR进程就可以获得CPU了。同时,这补充了耗尽进程的时间片。
④由于代码的其他部分已经决定了进程必须被移进或移出TASK_RUNNING状态,所以会经常使用schedule,例如,如果进程正在等待的硬件条件已经发生,所以如果必要,这个switch会改变进程的状态。如果进程已经处于TASK_RUNNING状态,它就无需处理了。如果它是可以中断的(等待信号),并且信号已经到达了进程,就返回TASK_RUNNING状态。在所以其他情况下(例如,进程已经处于TASK_UNINTERRUPTIBLE状态了),应该从运行队列中将进程移走。
⑤将p初始化为运行队列的第一个任务;p会遍历队列中的所有任务。
⑥c记录了运行队列中所有进程最好的“goodness”-具有最好“goodness”的进程是最易获得CPU的进程。goodness的值越高越好。
⑦遍历执行任务链表,跟踪具有最好goodness的进程。
⑧这个循环中只考虑了唯一一个可以调度的进程。在SMP模式下,只有任务不在cpu上运行时,即can_schedule宏返回为真时,才会考虑该任务。在UP情况下,can_schedule宏返回恒为真.
⑨如果循环结束后,得到c的值为0。说明运行队列中的所有进程的goodness值都为0。goodness的值为0,意味着进程已经用完它的时间片,或者它已经明确说明要释放CPU。在这种情况下,schedule要重新计算进程的counter;新counter的值是原来值的一半加上进程的静态优先级(priortiy),除非进程已经释放CPU,否则原来counter的值为0。因此,schedule通常只是把counter初始化为静态优先级。(中断处理程序和由另一个处理器引起的分支在schedule搜寻goodness最大值时都将增加此循环中的计数器,因此由于这个原因计数器可能不会为0。显然,这很罕见。)在counter的值计算完成后,重新开始执行这个循环,找具有最大goodness的任务。
⑩如果schedule已经选择了一个不同于前面正在执行的进程来调度,那么就必须挂起原来的进程并允许新的进程运行。这时调用switch_to来进行切换。
Linux系统能提供强大可靠的网络服务,并有管理程序对服务进行管理。例如我们熟悉的Web、FTP和电子邮件等,它们既可以单独运行,也可以被守护进程inetd调用,而且运行得都非常好。但我们不能仅停留在赞叹中,下面就给出两个服务程序程序和一个客户程序的例子,介绍服务程序和客户程序之间是如何沟通的。另外还要编辑配置一些文件,让服务程序也能接受服务管理程序管理。这两个服务程序功能相同,但一个是独立服务程序,另一个是被inetd调用的服务程序。这是TCP/IP网络服务的两大类,这里将两个程序放在一起是为了比较程序结构和运行方式。两服务程序都在Red Hat Linux 7.1和TurboLinux 7.0上调试通过。
独立服务器
TCP和UDP是两大TCP/IP数据传输方式,套接口是建立服务器客户机连接的机制,首先介绍它们建立通信联系的过程,然后给出一个TCP服务程序例子。
1.TCP套接口通信方式
对于TCP服务器端,服务程序首先调用建立套接口的函数socket(),然后调用绑定服务IP地址和协议端口号函数bind()。绑定成功后调用被动监听函数listen()等待客户连接,还要调用获取连接请求函数accept(),并一直阻塞到客户连接请求的到达,这个函数获取客户机IP地址和协议端口号。
对于TCP客户端,客户程序启动后后调用建立套接口函数socket(),然后调用连接函数connect(),此函数与服务器通过三次握手建立连接。
服务器和客户机建立连接后,就可以使用读函数read()和写函数write()收发数据了。数据交换完成后便各自调用关闭套接口函数close()删除套接口。TCP套接口通信方式见图1所示。
图1 TCP套接口通信方式
2.UDP套接口通信方式
UDP程序与TCP的区别是无需建立连接。服务器首先启动,然后等待用户请求。客户机启动后便直接向服务器请求服务,服务器接到请求后给出应答。
对于UDP服务器端,服务程序首先调用套接口函数socket(),然后调用绑定IP地址和协议端口号函数bind()。之后调用函数recvfrom()接收客户数据,调用sendto()向客户发送数据。
对于UDP客户端,客户机程序启动后调用套接口函数socket(),然后调用sendto()向服务器发送数据,调用recvfrom()接收服务器数据。
双方数据交换成功后,各自调用关闭套接口函数close()关闭套接口。UDP套接口通信方式见图2所示。
图2 UDP套接口通信方式
下面给出独立服务程序的例子。这个程序虽然简单,但是与复杂程序有着相同的结构。
//程序名:server.c
//功能:服务器从客户机读入一个字符,并将排在此字符后面的字符回送客户机
//服务器端口:9000
#include "sys/types.h"
#include "sys/socket.h"
#include "stdio.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#include "unistd.h"
int main()
{
int pid//用于存放fork()执行结果
int server_sockfd,client_sockfd//用于服务器和客户机套接口描述符
int bind_flag,listen_flag//用于存放bind()和listen()执行结果
int server_address_length,client_address_length//作为服务器客户机地址长变量
struct sockaddr_in server_address//作为服务器地址结构变量(含地址和端口)
struct sockaddr_in client_address//作为客户机地址结构变量(含地址和端口)
if((pid=fork())!=0) //用fork()产生新进程
exit(0)
setsid() //以子进程开始下面的程序
函数socket(),创建一个套接口,成功则返回套接口描述符。
server_sockfd=socket(AF_INET,SOCK_STREAM,0)
if(server_sockfd<0)
{
printf(“socket error /n”)
exit(1)
}
server_address.sin_family=AF_INET
函数htonl()用于将32位主机字节顺序转换为网络字节顺序,其中参数INADDR_ANY表示任何IP地址。
server_address.sin_addr.s_addr=htonl(INADDR_ANY)
函数htons()用于将16位主机字节顺序转换为网络字节顺序,其中的参数是绑定的端口号,读者可根据环境自行改动,目的是不与其它服务端口冲突。
server_address.sin_port=htons(9000)
server_address_length=sizeof(server_address)
函数bind()用于绑定本地地址和服务端口号,若调用成功返回值为0。
bind_flag=bind(server_sockfd,/
(struct sockaddr *)&server_address,/
server_address_length)
if(bind_flag<0)
{
printf(“bind error /n”)
exit(1)
}
函数listen(),指明服务器的队列长度,被动等待客户连接,调用成功返回值为0。
listen_flag=listen(server_sockfd,5)
if(listen_flag<0)
{
printf(“listen error /n”)
exit(1)
}
while(1)
{
char ch
函数accept()等待和获取用户请求,为每个新连接请求创建一个新的套接口,调用成功返回新套接口描述符。
client_sockfd=accept(server_sockfd,/
(struct sockaddr *)&client_address,/
&client_address_length)
函数read()和write()用于在服务器和客户机之间传送数据,调用成功返回读和写的字节数。
函数close(),用于程序使用完一个套接口后关闭套接口,调用成功返回值0。其中的参数为accept()创建的套接口的描述符client_sockfd。
read(client_sockfd,&ch,1)
printf(“cli_ch=%c”,ch)
ch++
write(client_sockfd,&ch,1)
close(client_sockfd)
}
}
程序完成后就可以使用命令进行编译。在命令行中输入“gcc -o server server.c”,将server.c编译成可执行程序server,这时便可用客户程序进行测试。在命令行执行“./server”启动服务程序,执行“netstat -na”查看有无server的服务端口。如果存在,则执行下面编写的客户程序“./client”。不过这仅是手工启动的方法,下面给出用服务管理程序管理server程序的方法。只要在目录/etc/rc.d/init.d下放入服务程序的脚本就能被服务程序读到。在命令行执行“touch server”创建文件server,并将文件属性改成可执行。在管理程序中并不能看到此服务名,脚本文件必须有一些结构才能被管理程序认为是服务程序脚本。
为了减少工作量,拷贝/etc/rc.d/init.d下脚本httpd,将拷贝脚本名命名为server,然后对其编辑。
(1)执行“cp httpd server”。
(2)用文本编辑器vi(其它编辑器亦可)将server打开进入编辑状态。首先用字符串server替换httpd。然后找到daemon server行,如果编写的程序放在变量PATH目录中,不需要修改此行;如果把服务程序放在其它目录中,就要写服务的全路径。例如程序在/root的目录中,就要写成daemon /root/server,还要删除“rm -f /var/run/server.pid”这一行。
(3)执行“chmod 755 server”,将server属性设定为可执行。
此时就可以用chkconfig、ntsysv等工具,在希望的运行级中增加这个新服务程序,然后测试客户机与服务器能否通信。
被xinetd调用的服务程序
在Linux系统中,有很多服务是被xinetd(较早版本使用的是inetd)超级守护服务器启动的。其实凡是基于TCP和UDP的服务都可使用超级守护进程启动,只是在服务量很大影响效率的情况下不被采用。
1.依赖xinetd启动的服务建立通信过程
为了与独立服务器程序比较,我们看一下依赖xinetd的服务器是如何启动的。
(1)xinetd启动时读取/etc/xinetd目录中的文件(早期版本为/etc/inetd文件),根据其中的内容给所有允许启动的服务创建一个指定类型的套接口,并将套接口放入select()中的描述符集合中。
(2)对每个套接口绑定bind(),所用的端口号和其它参数来自/etc/xinetd目录下每个服务的配置文件。
(3)如果是TCP套接口就调用函数listen(),等待用户连接。如果是UDP套接口,就不需调用此函数。
(4)所有套接口建立后,调用函数select()检查哪些套接口是活动的。
(5)若select()返回TCP套接口,就调用accept()接收这个连接。如果为UDP,就不需调用此函数。
(6)xinetd调用fork()创建子进程,由子进程处理连接请求。
◆ 子进程关闭所有其它描述符,只剩下套接口描述符。这个套接口描述符对于TCP是accept()返回的套接口,对于UDP为最初建立的套接口。然后子进程连续三次dup()函数,将套接口描述符复制到0、1和2,它们分别对应标准输入、标准输出和标准错误输出,并关闭套接口描述符。
◆ 子进程查看/etc/xinetd下文件中的用户,如果不是root用户,就用调用命令setuid和setgid将用户ID和组ID改成文件中指定的用户。
(7)对于TCP套接口,与用户交流结束后父进程需要关闭已连接套接口。父进程重新处于select()状态,等待下一个可读的套接口。
最后调用配置文件中指定的外部服务程序,外部程序启动后就可与用户进行信息传递了。
2.为xinetd编写专门的服务程序
除了独立服务程序能被xinetd启动外,还可以为xinetd编写专门的程序。此处的例子程序与上面server.c功能相同。不过两者的程序区别是很大的,此例的代码仅相当于上面传输数据的部分。我们还将程序名定为server.c,所以不能放在相同目录中,同名仅是为了和上面程序对照。
#include "unistd.h"
int main()
{
char ch
read(0,&ch,1)
ch++
write(1,&ch,1)
}
将程序编译成可执行文件,并做些设置就可被xinetd启动。注意不要和上面的独立服务程序server一起启动,因为客户程序写得比较简单,访问的是固定端口,服务器都设成了相同的端口号。
(1)编辑/etc/services文件,在行末增加一条记录:
server 9000/tcp
(2)在目录/etc/xinetd.d下编写文件server,内容为:
service server
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /home/test/server (此处设置成自己程序所在的目录)
}
如果使用的是较早版本,则需在/etc/inetd.conf文件中添加下面的行:
server tcp nowait root /path/to/yourdirectory/server
(3)执行/etc/rc.d/initd.d/xinetd restart重新启动xinetd服务器。早期版本执行/etc/rc.d/initd.d/inetd restart重新启动inetd。
(4)执行netstat -an查看有没有server程序使用的端口号,如果有就可使用下面客户机程序进行测试了。
客户机程序
下面就客户机函数做一简单介绍。
//程序名client.c
/*功能:从客户的控制台输入一个字符,然后将这个字符送到服务器,并将服务器返回的字符显示出来*/
#include "sys/types.h"
#include "sys/socket.h"
#include "stdio.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#include "unistd.h"
int main()
{
int sockfd//
int address_len
int connect_flag
struct sockaddr_in address
int connect_result
char client_ch,server_ch
函数socket()用于建立一个套接口,创建成功返回套接口描述符。
sockfd=socket(AF_INET,SOCK_STREAM,0)
if(sockfd<0)
{
printf(“sockfd error /n”)
}
address.sin_family=AF_INET
address.sin_addr.s_addr=inet_addr(“192.168.0.1”)/*读者根据自己环境改成服务器地址*/
address.sin_port=htons(9000)
address_len=sizeof(address)
函数connect()用于与服务器建立一个主动连接,调用成功返回值为0。
connect_flag=connect(sockfd,(struct sockaddr *)&address,address_len)
if(connect_flag==-1)
{
perror(“client”)
exit(1)
}
printf(“Input a character :”)
函数scanf()用于从控制台输入一个字符,并将字符存入client_ch的地址。函数write()和read()用于传输数据。函数printf()在客户机屏幕上显示服务器传回的字符。函数close()关闭套接口。
scanf(“%c”,&client_ch)
write(sockfd,&client_ch,1)
read(sockfd,&server_ch,1)
printf(“character from server : %c/n”,server_ch)
close(sockfd)
exit(0)
}
执行命令“gcc -o client client.c”,将client.c编译成client。执行“./client”,在程序提示下输入一个字符,就能看到服务器传回的字符。
以上介绍的仅是简单的例子。平时见到的服务程序远比它复杂,而且很多是多协议服务程序或是多协议多服务程序。多协议服务程序就是在main()中分别创建供服务的TCP和UDP套接口。为每个服务分别写出相应程序好处是便于控制,但是这样每个服务都启动两个服务器,而它们的算法响应是一样的,就要耗费不必要的资源,并且出了问题排错也较困难。多服务是将不同的服务集成在一起由一个程序完成,可用一个数组表示服务,数组中的每一项表示某协议某服务的一种,这样很容易扩展程序的服务功能。
嵌入式实时 *** 作系统强调了什么?
系统最大的特点当然是嵌入。嵌入就意味着它和硬件的联系很强,对硬件的适配和依赖度都是很高的,对于系统移植的难易度也就成了评价嵌入实时系统的重要方面。嵌入式 *** 作系统移植的目的是使嵌入式 *** 作系统能在某个微处理器或微控制器上运行。比如VxWorks是商用 *** 作系统的有很多API函数及相关技术支持,所以移植和二次开发比较容易,但是移植成本较高。嵌入还意味着对于小型的硬件要求,主要是面对实时产生的数据,在数据产生终端进行快速的运算。所以系统极其强调灵敏性和实时性。
另一个特点就是实时。对于实时性的要求,有很多种实现措施。我们一一道来。首先就是分区处理的思想。对于核心的功能划分,使用户信息和系统程序和运行分列在不同的区划里,保证任务的有效有序运转。其次, 任务管理是嵌入式实时 *** 作系统的核心和灵魂,决定了 *** 作系统的实时性能。它通常包含优先级设置、多任务调度机制和时间确定性等部分。 嵌入式 *** 作系统支持多任务,每个任务都具有优先级,任务越重要,赋予的优先级应越高。优先级的设置分为静态优先级和动态优先级两种。静态优先级指的是每个任务在运行前都被赋予一个优先级,而且这个优先级在系统运行期间是不能改变的;动态优先级则是指每个任务的优先级(特别是应用程序的优先级)在系统运行时可以动态地改变。 嵌入式 *** 作系统支持多任务,每个任务都具有优先级。任务调度主要是协调任务对计算机系统资源的争夺使用。对系统资源非常匮乏的嵌入式系统来说,任务调度尤为重要,它直接影响到系统的实时性能。通常,多任务调度机制分为基于优先级抢占式调度和时间片轮转调度。
基于优先级抢占式调度:系统中每个任务都有一个优先级,内核总是将CPU分配给处于就绪态的优先级最高的任务运行。如果系统发现就绪队列中有比当前运行任务更高的优先级任务,就把当前运行任务置于就绪队列中,调入高优先级任务运行。系统采用优先级抢占方式进行调度,可以保证重要的突发事件及时得到处理。
时间片轮转调度:让优先级相同的处于就绪状态的任务按时间片使用CPU,以防止同优先级的某一任务长时间独占CPU。
在一般情况下,嵌入式实时 *** 作系统采用基于优先级抢占式调度与时间片轮转调度相结合的调度机制。
第三,在运行的执行方式上,VXworks等系统采用进程和线程的执行方式,而市面上其他的系统多采用任务执行方式。
嵌入式实时 *** 作系统函数调用与服务的执行时间应具有可确定性。系统服务的执行时间不依赖于应用程序任务的多少。基于此特征,系统完成某个确定任务的时间是可预测的。表1具体列出了4种 *** 作系统的调度机制。
VxWorks具有高效的任务管理功能,它支持多任务,可分配256个优先级,支持优先级抢占式调试和时间片轮转调度,实时性最好。μC/OS-II内核是针对实时系统的要求设计实现的,只支持基于固定优先级抢占式调度;调度方法简单,可以满足较高的实时性要求。μClinux在结构上继承了标准Linux的多任务实现方式,分为实时进程和普通进程,分别采用先来先服务和时间片轮转调度;仅针对中低档嵌入式CPU特点进行改良,且不支持内核抢占。eCos调度方法丰富,提供了两种基于优先级的调度器(即位图调度器和多级队列调度器),允许用户在进行配置时选择其中一个凋度器,适应性好。
此外,还涉及到任务的中断管理。
中断管理是实时系统中一个很重要的部分,系统经常通过中断与外部事件交互。主要考虑是否支持中断嵌套、中断处理机制、中断延时等。
以VXwoks为例,xWorks *** 作系统中断管理采用中断处理与普通任务分别在不同栈中处理的中断处理机制,使得中断只会引发一些关键寄存器的存储,而不会导致任务的上下文切换,从而极大地缩短了中断延时。同时,VxWorks的中断处理程序只能在最短时间内通告中断的发生,而将其他的非实时处理尽量放入被引发的中断服务程序中来完成,这也缩短了中断延时。但是凼为中断服务程序不在一个固定的仟务上下文中执行,而目没有任务控制块,所以所有中断服务程序使用相同的中断堆栈。为了能处理最坏情况下的中断嵌套,必须分配足够大的中断堆栈空间。
因商业原因,不对其他新型嵌入式实时 *** 作系统做分析。
·有哪些厉害的MPU实时 *** 作系统?
1、VXworks
VxWorks *** 作系统是美国WindRiver公司于1983年设计开发的一种嵌入式实时 *** 作系统(RTOS),是嵌入式开发环境的关键组成部分。良好的持续发展能力、高性能的内核以及友好的用户开发环境,在嵌入式实时 *** 作系统领域占据一席之地。它以其良好的可靠性和卓越的实时性被广泛地应用在通信、军事、航空、航天等高精尖技术及实时性要求极高的领域中,如卫星通讯、军事演习、d道制导、飞机导航等。在美国的 F-16、FA-18战斗机、B-2 隐形轰炸机和爱国者导d上,甚至连1997年4月在火星表面登陆的火星探测器、2008年5月登陆的凤凰号,和2012年8月登陆的好奇号也都使用到了VxWorks上。
VxWorks *** 作系统有以下部件组成:
多任务调度(采用基于优先级抢占方式,同时支持同优先级任务间的分时间片调度)
任务间的同步
进程间通信机制
中断处理
定时器和内存管理机制
总之,VxWorks的系统结构是一个相当小的微内核的层次结构。内核仅提供多任务环境、进程间通信和同步功能。这些功能模块足够支持VxWorks在较高层次所提供的丰富的性能的要求。
2、QNX
QNX是一种商用的遵从POSIX规范的类Unix实时 *** 作系统,目标市场主要是面向嵌入式系统。它可能是最成功的微内核 *** 作系统之一。
QNX成立于1980年,是加拿大一家知名的嵌入式系统开发商。
QNX的应用范围极广,包含了:控制保时捷跑车的音乐和媒体功能、核电站和美国陆军无人驾驶Crusher坦克的控制系统[2],还有RIM公司的BlackBerry PlayBook平板电脑。
3、Rtems
RTEMS, 即: 实时多处理器系统(Real Time Executive for Multiprocessor Systems),是一个开源的无版税实时嵌入 *** 作系统RTOS。
它最早用于美国国防系统,早期的名称为实时导d系统(Real Time Executive for Missile Systems),后来改名为实时军用系统(Real Time Executive for Military Systems),现在由OAR公司负责版本的升级与维护。无论是航空航天、军工,还是民用领域RTEMS都有着极为广泛的应用。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)