当前位置: 首页 > news >正文 门户网站微信服务号建设wordpress纯文字主题 news 2025/10/30 20:56:03 门户网站微信服务号建设,wordpress纯文字主题,济宁建站公司,湘潭学校网站建设 磐石网络文章目录Linux内核设计与实现第四章学习笔记具体场景Linux调度算法传统的调度公平调度调度的实现时间记账调度器实体虚拟实时进程选择调度器入口睡眠和唤醒抢占和上下文切换用户抢占内核抢占实时调度策略Linux内核设计与实现第四章学习笔记 进程优先级#xff1a;Linux中采用… 文章目录Linux内核设计与实现第四章学习笔记具体场景Linux调度算法传统的调度公平调度调度的实现时间记账调度器实体虚拟实时进程选择调度器入口睡眠和唤醒抢占和上下文切换用户抢占内核抢占实时调度策略Linux内核设计与实现第四章学习笔记 进程优先级Linux中采用了两种不同的优先级返回 nice值范围是-20到19默认为0值越大优先级越低实时优先级越高优先级越高范围是0到99 任何实时进程的优先级都高于普通的进程也就是说实时优先级和nice优先级是处于两个不同的范畴 时间片表明进程在被抢占前所能持续运行的时间调度策略规定必须有一个默认的时间片但是这并不好太长系统的交互性差太短增大进程切换的处理器消耗。并且IO型进程不需要太多时间片处理器消耗型则希望越长越好 Linux系统的CFS调度器并没有直接分配时间片到进程而且将处理器的使用比例给了进程这个比例会受nice值影响高nice值低权重低nice值高权重。 多数操作系统中是否要将一个进程立刻投入运行(抢占当前进程)完全由进程优先级和是否有时间片决定的。而Linux系统中使用的新的CFS调度器抢占时机取决于新进程消耗了多少处理器占比如果消耗的使用比 比当前进程小则新进程立刻投入运行抢占当前进程否则则继续等待 具体场景 假设有以下场景一个系统有两个可运行的进程一个文字编辑程序和视频编码程序。文字编辑显然是IO消耗型因为它大部分时间等待用户的键盘输入。相反视频编码是处理器消耗型除了从磁盘上读出原始数据和最后把视频输出外其他时间都是在对原始数据进程编码。 用户希望按下按键系统马上就能响应用户却分辨不出视频编码程序是立刻运行还是半秒钟后才开始当然视频编码程序越早完成越好 这样的场景里理想情况是调度器应该基于文字编辑程序相比于视频编码程序更多的处理器时间因为它是交互型。因而在多数操作系统中上述目标的达成是依赖系统分配给文字编辑器比视频编码程序更多时间片和更高优先级。 Linux则采用不同的方法不再分配既定的优先级和时间片而是分配一个处理器比。 如果两个进程是仅有的且又具有相同的nice值那么它们都会得到50%的处理器使用比但因为文本编辑器将更多时间用于等待用户输入因此它们肯定真的用到50%同时视频编码程序无疑有机会得到超50%的处理器时间 关键的来了当文本编辑程序被唤醒时我们首要目标是确保能在用户输入时马上运行。因而一旦文本编辑器被唤醒CFS注意到给它的处理器使用比是50%但是它实际上却用的非常少特别是CFS发送文本编辑器运行实际比视频编码程序少这种情况下为了兑现让所有进程公平的承诺CFS会立刻抢占视频编码程序让文本编辑程序投入运行。文本编辑程序运行以后处理掉用户的输入有一次进入睡眠等待用户下一次输入。 因为文本编辑程序并没有消费承诺给它的50%处理器使用比因此情况依旧CFS总是会毫不犹豫地让文本编辑程序在需要的时候投入运行视频处理程序只能在剩下的时间运行 Linux调度算法 Linux调度器是以模块方式提供的这样做的目的是允许不同类型的进程可以针对性的选择调度算法 这种模块化结构被称之为调度器类它允许多个不同的可动态添加的调度器算法并存调度属于自己范畴的进程每个调度器都有一个优先级基础的调度器代码定义在kernel/sched.c文件中他会按照优先级遍历调度器类然后由调度器类去选择下面要指定哪一个程序 传统的调度 现代进程调度器有两个通用的概念进程优先级和时间片。时间片是指进程运行多少时间进程一旦启动就会有一个默认时间片。具有更高优先级的进程将会运行得更频繁并且时间片也会被赋予得更多。在unix系统上优先级以nice值输出给用户空间这点听起来简单但却有很多问题 第一个问题若要把nice值映射到时间片就必须把nice单位值对应到处理器的绝对时间。但这样会导致进程切换无法最优化进行比如说假定我们把默认nice值(0)分配给一个进程——对应的是一个100ms的时间片同时再分配一个最高的nice值(20)给另一个进程——对应5ms。假定两个进程都可运行那么默认优先级进程会得到20/21 (105ms中的100)的处理器时间而低优先级的进程会获得1/21(105ms中的5ms)处理器时间。 再假如两个同等低优先级的进程我们希望它们各获得一半的处理器时间但是每次每个进程只能获得5ms(10ms中的5ms)也就是说上面的例子105进行一次进程切换现在却要10ms内两次进程切换 显然这些时间片的分配并不理想它们是给定nice值到时间片映射与进程运行优先级混合计算的共同作用结果。事实上高nice值(低优先级)的进程往往是后台进程且多数是计算密集型普通优先级的进程更多是前台用户任务 第二个问题假设我们有两个进程分别具有不同的优先级第一个假设nice值只是0第二个假设是1它们分别映射到时间片100ms和95ms那么它们的时间片几乎一样差别微乎其微。但是如果进程分别被赋予18和19的nice值则它们分别被映射为10ms和5ms。如果这样前者比后者获得两倍的处理器时间不过nice值通常都使用相对值也就是说把进程的nice值减小带来的效果极大取决于nice值的初始值 第三个问题如果执行nice值到时间片的映射我们需要能分配一个绝对时间片并且这个绝对时间片必须能在内核的测试范围内。多数操作系统中上述要求意味着时间片必须是节拍器的整数倍但是这么做又引发一些问题。首先最小时间片比如是定时器节拍的整数倍也就是10ms或1ms的倍数其次系统定时器限制了两个时间片的差异连续的nice值映射到时间片其差别范围多至10ms少则1ms。最后时间片还会随着定时器节拍改变而改变 第四个问题你可能为了进程能够更快的投入运行而对新唤醒的进程提升优先级即使它们的时间片已用完虽然这种办法能够提升不少交互性能但是还是有一些例外比如说给某些特殊的睡眠/唤醒的进程一个玩弄调度器的后门使得打破公平的原则损害其他进程的利益 可以把nice值呈几何增长而不是算术增长这样可以解决问题二 采用新的度量机制把nice值到时间片的映射与定时器节拍分离开这样可以解决问题三 但是这些方案都避开了本质的问题——分配绝对的时间片引发固定的切换频率 公平调度 CFS出发点基于一个简单的理念进程调度的效果应该如同系统具备一个理想中的完美的多任务处理器在这种系统中每个进程能够获得1/n处理器时间n是指可运行进程的数量。同时我们可以调度给它无限小的时间周期在任何可测量周期内我们给予n个进程中每个进程同样多的运行时间。 举个例子 有两个进程我们先运行一个5ms另一个亦然但他们任何一个运行时都占有100%的处理器在理想情况下完美的多任务处理器模型应该是这样的我们能在10ms内同时运行两个进程它们各自使用处理器的一半 当然这个只是理想模型我们无法在一个处理器中真的同时运行多个进程而且每个进程运行无限小的时间周期也是不对的因为调度时进程抢占也会有一定的代价将一个进程换出另一个换入本身就有消耗同时还会影响到缓存的效率所以我们喜欢所有进程只运行一个非常短的周期并考虑这将带来的消耗 CSF的做法是允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程而不再采用分配给每个进程时间片的做法了。CFS在所有可运行进程总数的基础上计算出一个进程应该运行多久而不是依靠nice值来计算时间片。 nice值在CFS中被作为进程获得的处理器运行比的权重更高的nice值(越低的优先级)进程获得更低的处理器使用权重。这是相对默认nice值进程的进程而言的 CFS为完美多任务中的无限小调度周期的近似值设立了一个目标这个目标称之为目标延迟越小的调度周期将带来越好的交互性同时还引入了时间片的底线称之为最小粒度确保每个进程最小获得1ms的运行时间同时切换进程的消耗被限制在一定范围内 nice值对时间片不再是算术加权而是几何加权任何nice值对应的相对时间不再是一个绝对值而是处理器的使用比 调度的实现 相关代码位于kernel/shced_fair.c文件中我们将特别关注四个组成部分 时间记账进程选择调度器入口睡眠和唤醒 时间记账 调度器实体 所有的调度器都必须对进程的运行时间做记账即使CFS不再有时间片的概念但是它也必须维护进程运行的时间记账因为它需要确保每个进程公平分配给它的处理器时间内运行CFS使用调度器实体结构(定义在linux/sched.h中的struct_sched_entity)来追踪进程运行记账 虚拟实时 vruntime变量存放进程的虚拟运行时间该运行时间的计算是经过所有可运行进程总数的计算虚拟时间是以ns为单位的所以vruntime和定时器节拍不再相关。虚拟运行时间可以帮助我们逼近理想模型显然相同优先级的进程的虚拟运行时间都是相同的当然处理器无法实现完美的多任务它必须依次运行每个任务因此CFS使用vruntime变量来记录一个程序到底运行了多长时间以及它应该再运行多久 定义在kernel/sched.fair.c文件中的update_cur函数实现了记账功能 update_cur()函数计算了当前进程的执行时间并且将其存放在变量delta_exec中然后它又将运行时间传递给_update_cur()函数后者再根据当前进程总数对运行时间进行加权计算最终将上诉权重值与当前运行进程的vruntime相加 update_cur由系统定时器周期性调用的无论是在进程处于可运行状态还是被阻塞处于不可运行状态。根据这种方法vruntime可以准确的测量给定进程的运行时间而且可以知道谁应该是下一个被运行的进程 进程选择 当CFS需要选择下一个运行进程的时候它会挑选一个具有最小vruntime的进程这其实就是CFS调度算法的核心选择具有最小vruntime的任务那么剩下的我们只需要关心如何选择最小的vruntime即可 CFS选择红黑树来组织可运行进程队列并利用其迅速找到最小vruntime值的进程在linux中红黑树被称之为rbtree他是一个自平衡二叉搜索树 挑选下一个任务有那么一颗红黑树存储了系统中所有可运行进程其中节点的键值便是可运行进程的虚拟运行时间CFS只要选择具有最小vruntime的叶子节点也就是最左侧的叶子节点实现这一过程的函数称之为_pick_next_entity()它定义在kernel/sched_fair.c中。这个函数的返回值便是CFS调度器选择的下一个运行进程如果该函数返回值是NULL那么表示没有最左叶子节点也就是说树中没用任何节点这种情况下表示系统中没有任何可运行进程CFS调度器便会选择idle任务运行 值得注意的是这个函数并不会遍历树找到最左叶子节点因为该值已经缓存在rb_leftmost字段中 向树中加入进程在进程变为可运行状态(被唤醒)或者是通过fork()调用第一次创建进程时候CFS会将进程加入红黑树中以及缓存最左侧叶子节点enqueue_entity()函数实现了这一切 该函数更新运行时间和其他一些统计数据然后调用__enqueue_entity()进行繁重的插入操作 从树中删除进程删除动作发生在进程阻塞(变成不可运行状态)或者终止时(结束运行) 其实和红黑树添加进程一样实际工作由辅助函数_enqueue_entity完成 唯一要注意的是如果删除的节点是最左侧节点那么要调用rb_next()按顺序遍历找到谁是下一个节点 调度器入口 进程调度的主要入口是函数schedule()它定义在文件kernel/sched.c中它正是内核其他部分用于调用进程调度器的入口选择哪个进程可以运行何时将其投入运行。schedle()通常都需要和一个具体的调度器类相关联也就是说他会找到一个最高优先级的调度类——后者需要有自己的可运行队列然后问后者谁才是下一个该运行的进程。所以该函数唯一做的重要的事情就是pick_next_task()(kernel/sched.c)pick_next_task()会以优先级为序从高到底依次检查每一个调度器类并且从最高优先级的调度器类中选择最高优先级的进程 每个调度器类都是先了pick_next_task函数CFS中的pick_next_task会调用pick_next_entity该函数会调用__pick_next_entity函数 睡眠和唤醒 休眠通过等待队列进行处理等待队列是由等待某些事件发生的进程组成的简单链表内核用wake_queu_head_t表示等待队列。等待队列可以通过DECLARE_WAITQUEUE静态创建也可以由init_waitqueue_head()动态创建进程把自己放入等待队列并设置成不可运行状态 进程通过执行下面几个步骤把自己加入一个等待队列中 调用宏DEFINE_WAIT创建一个等待队列的项调用add_wait_queue把自己加入到队列中该队列会在进程等待的条件满足时唤醒它所以我们必须在某个地方撰写相关代码在事件发生的时候对等待队列执行wake_up操作调用prepare_to_wait方法把进程状态变更为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE。而且该进程如果有必要的话会把进程加回到等待队列中如果状态被设置为TASK_INTERRUPTIBLE则信号唤醒进程这就是所谓的伪唤醒因此检查并处理信号当进程被唤醒的时候它会再次检查条件是否为真如果是就退出循环如果不是它再次调用schedule并一直重复这步操作当条件满足后进程将自己设置为TASK_RUNNING并调用finish_wait函数把自己移出等待队列 函数inotify_read位于文件fs/notify/inotify/inotify_user.c文件中负责从文件描述符中读取信息 唤醒操作通过调用wake_up函数进程他会唤醒等待队列上的所有进程它调用函数try_to_wake_up该函数负责将进程设置为TASK_RUNNING状态调用enqueue_task将此进程放入红黑树中如果被唤醒的进程优先级比当前正在执行的进程的优先级高还要设置need_resched标志通常哪一段代码促使等待条件达成他就要负责随后调用wake_up函数比如说当磁盘数据到来时VFS就要负责对等待队列调用wake_up函数以便唤醒队列中等待这些数据的进程 值得注意的是存在虚假的唤醒有时候进程被唤醒并不是因为它所等待的条件达成了 抢占和上下文切换 上下文切换也就是从一个可执行进程切换到另一个可执行进程由dkernel/sched.c中的context_switch函数负责每当一个新的进程被选处理准备投入运行的时候schedule()函数就会调用此函数它完成两项基本的任务 调用声明在asm/mmu_context.h中的switch_mm()该函数负责把虚拟内存从上一个进程映射切换到新进程中调用声明在asm/sysytem.h中的switch_to()函数该函数负责从上一个进程处理器状态切换到新进程的处理器状态这包括保存、恢复栈信息和寄存器信息。还有其他任何与体系相关的状态信息都必须以进程为对象进程管理和保存 内核必须知道什么时候调用schedule()如果仅靠用户程序显式的调用schedule他们可能就会永远执行下去。所以内核提供一个need_resched标志表明是否需要重新执行一次调度当某个优先级高的进程进入可执行状态的时候try_to_wake_up也会设置这个标志内核检查该标志确认其被设置调用schedule()来切换一个新的进程该标志对内核来说表示尽快调用调度程序 函数目的set_task_need_resched()设置指定进程中的need_resched标志clear_tsk_need_resched()清楚指定进程中的need_reschedule标志need_reschedule检查need_reschedule标志的值如果被设置就返回真反之返回假 再返回用户空间以及从中断返回的时候内核也会检查need_resched标志 每一个进程都包含这个标志这是因为访问进程描述符内的数值要比访问一个全局变量快(因为current宏速度很快并且描述符通常都在高速缓存中) 用户抢占 内核即将返回用户空间的时候如果need_reschedule标志被设置那么会调用schedule此时就会发生用户抢占所以内核无论再中断处理处返回还是再系统调用后返回都会检查need_resched标志如果它被设置那么内核会选择一个其他的进程投入运行。 也就是说用户抢占再以下情况下发生 从系统调用返回用户空间时从中断处理程序返回用户空间时 内核抢占 只要重新调度是安全的内核就可以再任何时间抢占正在执行的任务 那么什么时候重新调度才是安全的呢?只要没有持有锁内核就可以进行抢占锁是非抢占区域的标志 为了支持内核抢占所作的第一处改动就是为每个进程thread_info引入preempt_count计数器该计数器初始值为0每当使用锁的时候数值1释放锁的时候数值-1当数值为0表示可以抢占。从中断返回内核空间的时候内核会检查need_reschedule标志和preemmpt_count的值如果need_reschedule被设置并且preempt_count为0的话这说明有一个更为重要的任务需要执行并且可以安全的抢占。如果此时prermpt_count不为0说明当前任务持有锁抢占就是不安全的此时内核就会像通常一样从中断处返回当前执行进程。 如果内核中的进程都被阻塞了或它显式的调用schedule内核抢占也会显式的发生 所以内核抢占会发生在 中断处理程序证字啊执行且返回内核空间之前内核代码再一次具有可抢占性的时候如果内核中的任务显式调用schedule内核中的任务被阻塞(其实也是因为被阻塞会显式调用schedule) 实时调度策略 Linux提供了两种实时调度策略SCHED_FIFO, SCHED_RR。普通的、非实时的调度策略是SCHED_NORMAL。借助调度器的框架这些实时策略并不被完全公平策略来管理而是被一个特殊的实时调度器管理 FIFO一旦一个FIFO进程处于可运行状态就会一直执行知道它自己受阻塞或者显式的释放处理器为止它不基于时间片可以一直执行下去只有更高优先级的FIFO或者RR才能抢占它 RR也类似 这两种实时算法都是静态优先级的 文章转载自: http://www.morning.qdlr.cn.gov.cn.qdlr.cn http://www.morning.tjsxx.cn.gov.cn.tjsxx.cn http://www.morning.yznsx.cn.gov.cn.yznsx.cn http://www.morning.lmdfj.cn.gov.cn.lmdfj.cn http://www.morning.nmfxs.cn.gov.cn.nmfxs.cn http://www.morning.qineryuyin.com.gov.cn.qineryuyin.com http://www.morning.jcbmm.cn.gov.cn.jcbmm.cn http://www.morning.frfnb.cn.gov.cn.frfnb.cn http://www.morning.gqcd.cn.gov.cn.gqcd.cn http://www.morning.pwghp.cn.gov.cn.pwghp.cn http://www.morning.ypxyl.cn.gov.cn.ypxyl.cn http://www.morning.qytyt.cn.gov.cn.qytyt.cn http://www.morning.bnxnq.cn.gov.cn.bnxnq.cn http://www.morning.bslkt.cn.gov.cn.bslkt.cn http://www.morning.wdjcr.cn.gov.cn.wdjcr.cn http://www.morning.qxbsq.cn.gov.cn.qxbsq.cn http://www.morning.czgfn.cn.gov.cn.czgfn.cn http://www.morning.ydxwj.cn.gov.cn.ydxwj.cn http://www.morning.zxhpx.cn.gov.cn.zxhpx.cn http://www.morning.zckhn.cn.gov.cn.zckhn.cn http://www.morning.rqfzp.cn.gov.cn.rqfzp.cn http://www.morning.pwmpn.cn.gov.cn.pwmpn.cn http://www.morning.nlglm.cn.gov.cn.nlglm.cn http://www.morning.ntlxg.cn.gov.cn.ntlxg.cn http://www.morning.jfwbr.cn.gov.cn.jfwbr.cn http://www.morning.qglqb.cn.gov.cn.qglqb.cn http://www.morning.plchy.cn.gov.cn.plchy.cn http://www.morning.smmrm.cn.gov.cn.smmrm.cn http://www.morning.gcqkb.cn.gov.cn.gcqkb.cn http://www.morning.mgkb.cn.gov.cn.mgkb.cn http://www.morning.bxrlt.cn.gov.cn.bxrlt.cn http://www.morning.fhwfk.cn.gov.cn.fhwfk.cn http://www.morning.myxps.cn.gov.cn.myxps.cn http://www.morning.llthz.cn.gov.cn.llthz.cn http://www.morning.gwyml.cn.gov.cn.gwyml.cn http://www.morning.lzqtn.cn.gov.cn.lzqtn.cn http://www.morning.clwhf.cn.gov.cn.clwhf.cn http://www.morning.mspkz.cn.gov.cn.mspkz.cn http://www.morning.ntwxt.cn.gov.cn.ntwxt.cn http://www.morning.rqgbd.cn.gov.cn.rqgbd.cn http://www.morning.ntnml.cn.gov.cn.ntnml.cn http://www.morning.tftw.cn.gov.cn.tftw.cn http://www.morning.wqkfm.cn.gov.cn.wqkfm.cn http://www.morning.zqsnj.cn.gov.cn.zqsnj.cn http://www.morning.yhplt.cn.gov.cn.yhplt.cn http://www.morning.bfsqz.cn.gov.cn.bfsqz.cn http://www.morning.yhxhq.cn.gov.cn.yhxhq.cn http://www.morning.tbwsl.cn.gov.cn.tbwsl.cn http://www.morning.a3e2r.com.gov.cn.a3e2r.com http://www.morning.zlnmm.cn.gov.cn.zlnmm.cn http://www.morning.wpydf.cn.gov.cn.wpydf.cn http://www.morning.ngkng.cn.gov.cn.ngkng.cn http://www.morning.pkdng.cn.gov.cn.pkdng.cn http://www.morning.dhdzz.cn.gov.cn.dhdzz.cn http://www.morning.pfntr.cn.gov.cn.pfntr.cn http://www.morning.jqpq.cn.gov.cn.jqpq.cn http://www.morning.ztmnr.cn.gov.cn.ztmnr.cn http://www.morning.klwxh.cn.gov.cn.klwxh.cn http://www.morning.kcfnp.cn.gov.cn.kcfnp.cn http://www.morning.kjdxh.cn.gov.cn.kjdxh.cn http://www.morning.qgqck.cn.gov.cn.qgqck.cn http://www.morning.jkfyt.cn.gov.cn.jkfyt.cn http://www.morning.nggbf.cn.gov.cn.nggbf.cn http://www.morning.mszwg.cn.gov.cn.mszwg.cn http://www.morning.wjplm.cn.gov.cn.wjplm.cn http://www.morning.yfphk.cn.gov.cn.yfphk.cn http://www.morning.gbcxb.cn.gov.cn.gbcxb.cn http://www.morning.zdnrb.cn.gov.cn.zdnrb.cn http://www.morning.rkbly.cn.gov.cn.rkbly.cn http://www.morning.mzhh.cn.gov.cn.mzhh.cn http://www.morning.rlzxr.cn.gov.cn.rlzxr.cn http://www.morning.qtxwb.cn.gov.cn.qtxwb.cn http://www.morning.lwtfr.cn.gov.cn.lwtfr.cn http://www.morning.pcwzb.cn.gov.cn.pcwzb.cn http://www.morning.ddfp.cn.gov.cn.ddfp.cn http://www.morning.rqrxh.cn.gov.cn.rqrxh.cn http://www.morning.ypzsk.cn.gov.cn.ypzsk.cn http://www.morning.cklld.cn.gov.cn.cklld.cn http://www.morning.fglzk.cn.gov.cn.fglzk.cn http://www.morning.qmkyp.cn.gov.cn.qmkyp.cn 查看全文 http://www.tj-hxxt.cn/news/263331.html 相关文章: 网站后台怎么修改前台的某个超链接网址泰安房产网站 淮南做网站的公司有哪些.net网站开发流程 燕郊医院网站建设网站搭建推广优化 中国大唐集团公司招聘网站网站备案 取消接入 温州营销推广公司台州seo网站排名优化 解决方案网站排名毕业设计做网站教程 长沙建设品牌网站mip wordpress模板 存量权益登记在哪个网站上做wordpress免费装修主题 网站自动加水印上海的建设项目招投标在哪个网站 网站后缀类型如何做adsense网站 福州市网站建设公司山东省住房和城乡建设局网站 网站搜索优化公司安康市信息平台 许昌市城市建设局网站适合做网站开发的电脑配置 创新建设资金网站网站用户建设的设计与实现 东莞大型网站建设现在外地人能不能进广州 jsp网站开发 英文桂林做网站哪家公司好 做瑜珈孕妇高清图网站wordpress options framework 企业建设网站价格单网站建设大概费用 手机支付网站开发淮北人论坛招聘网 江苏住房和城乡建设厅官方网站邯郸市教育考试院网站 高端网站开发成本logo免费设计在线生成下载 做医采官方网站wordpress 修订 wordpress 发布外链昆明官网seo诊断 网站验收技术指标玉田住房和建设局网站 东营网站建设电话零元开店的电商平台 做营销型网站的企业网站合作建设方案 手机网站开发项目如何注册品牌名称和商标 可以做雷达图的网站wordpress下拉菜单的阴影怎么改 龙岩做网站公司在哪里手机网站 跳转 河南省工程建设业协会网站口碑好网站建设多少钱