注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

内核队列默认唤醒顺序及script脚本内参数处理  

2013-11-20 22:42:59|  分类: Linux内核 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
零、两部分没啥关系,分开写好像太矫情了,所以就这么没羞没臊的挤在一起了。


一、等待队列的优先唤醒问题

1、终端前台进程
主要是为了验证下fork出来的进程默认是否和父进程在相同的进程组中,所以写了个程序简单测试了一下,结论是fork之后的进程和父进程是在同一个进程组。这个问题有意义的原因在于默认情况下,对于一个终端,只有它的前台进程组才能够从终端上读取输入数据,不在前端,则会出现SIGTIN信号,而这个信号通常是致命的。
内核中相关代码为read_chan-->>job_control
static int job_control(struct tty_struct *tty, struct file *file)
{
    /* Job control check -- must be done at start and after
       every sleep (POSIX.1 7.1.1.4). */
    /* NOTE: not yet done after every sleep pending a thorough
       check of the logic of this change. -- jlc */
    /* don't stop on /dev/console */
    if (file->f_op->write != redirected_tty_write &&
        current->signal->tty == tty) {
        if (!tty->pgrp)
            printk("read_chan: no tty->pgrp!\n");
        else if (task_pgrp(current) != tty->pgrp) {
            if (is_ignored(SIGTTIN) ||
                is_current_pgrp_orphaned())
                return -EIO;
            kill_pgrp(task_pgrp(current), SIGTTIN, 1);
            return -ERESTARTSYS;
        }
    }
    return 0;
}
2、测试代码
harry:/home/tsecer/pgroup #cat pgrp.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
        int pid = fork();
        if (pid == 0)
        {
                int subpid = fork();
                char buff[100];
                int iRet = 0;
                while (iRet = read(0, buff, sizeof buff))
                {
                        printf("pid %d iRet %d buff[0] %c\n", getpid(), iRet, buff[0]);
                }
        }
        sleep(1000);
}
harry:/home/tsecer/pgroup #gcc pgrp.c
harry:/home/tsecer/pgroup #./a.out
sdf
pid 2778 iRet 4 buff[0] s
s
dpid 2778 iRet 2 buff[0] s
f
pid 2778 iRet 3 buff[0] d
sd
pid 2778 iRet 3 buff[0] s
f
pid 2778 iRet 2 buff[0] f
dsf
pid 2778 iRet 4 buff[0] d
ds
pid 2779 iRet 3 buff[0] d
f
pid 2779 iRet 2 buff[0] f
测试程序证明了两个事情,一个是fork出来的子进程默认和父进程在同一个进程组中,第二点就是对于竞争同一个终端的进程,被唤醒的顺序没有特定顺序。
3、内核中读取等待队列的实现
read_chan-->>add_wait_queue--->>__add_wait_queue
/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
#ifndef CONFIG_DEBUG_LIST
static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}
#else
extern void list_add(struct list_head *new, struct list_head *head);
#endif
也就是默认情况下是按照栈来处理等待队列的,所以在例子中一个进程请求结束之后,放入一个先进先出队列,所以从现象上看,通常一个进程总是连续出现。
4、加入等待队列时逻辑
void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
    unsigned long flags;

    wait->flags &= ~WQ_FLAG_EXCLUSIVE;
    spin_lock_irqsave(&q->lock, flags);
    __add_wait_queue(q, wait);
    spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue);

void fastcall add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait)
{
    unsigned long flags;

    wait->flags |= WQ_FLAG_EXCLUSIVE;
    spin_lock_irqsave(&q->lock, flags);
    __add_wait_queue_tail(q, wait);
    spin_unlock_irqrestore(&q->lock, flags);
}
对于互斥型的唤醒操作,新来的请求放入最后;共享型的请求,放在前面。而互斥型的通常用在内核的设备驱动中。对于我们看到的tty例子,所有的进程是共享的,所以当有输入时,它们被全部唤醒,此时就会产生所谓的“惊群效应”。好消息是虽然被惊醒了队列上的所有对象,但是内核中对于这种情况都是有循环的预防处理,所有这次意外的惊醒(被唤醒但是没有读到数据)不会呈现给用户态。我现在使用的内核比较老,所以在/proc/pid/status文件中看不到进程切换次数,在新的内核中可以验证下两个进程的调度次数均有增加。
在read_chan中,对其处理为

    add_wait_queue(&tty->read_wait, &wait);
    while (nr) {
    if (!input_available_p(tty, 0)) {
            if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
                retval = -EIO;
                break;
            }
            if (tty_hung_up_p(file))
                break;
            if (!timeout)
                break;
            if (file->f_flags & O_NONBLOCK) {
                retval = -EAGAIN;
                break;
            }
            if (signal_pending(current)) {
                retval = -ERESTARTSYS;
                break;
            }
            n_tty_set_room(tty);
            timeout = schedule_timeout(timeout);
            continue;
        }
    }
    mutex_unlock(&tty->atomic_read_lock);
    remove_wait_queue(&tty->read_wait, &wait);
注意的是,进程被唤醒的同时并不会被从等待队列中删除掉,也就是说,被添加在队列上,唤醒后如果没有主动把自己从队列中删除掉,当事件促使队列上等待者被唤醒(本例中就是终端有输入)时,该等待者同样会被唤醒。
二、内核对脚本文件参数处理
1、代码示例
主要是想在script的命令行传入多个参数,一个是field分隔符,然后通过-f指明这个脚本是一个awk脚本
harry:/home/tsecer/script #cat script.awk
#!/bin/awk -F'|' -f
END {print "end"}
harry:/home/tsecer/script #./script.awk
awk: ./script.awk
awk: ^ syntax error
awk: ./script.awk
awk:   ^ unterminated regexp
2、内核对脚本内参数处理
binfmt_script.c:load_script
    /*
     * OK, we've parsed out the interpreter name and
     * (optional) argument.
     * Splice in (1) the interpreter's name for argv[0]
     *           (2) (optional) argument to interpreter
     *           (3) filename of shell script (replace argv[0])
     *
     * This is done in reverse order, because of how the
     * user environment and arguments are stored.
     */
    remove_arg_zero(bprm);
    retval = copy_strings_kernel(1, &bprm->interp, bprm);
    if (retval < 0) return retval;
    bprm->argc++;
    if (i_arg) {
        retval = copy_strings_kernel(1, &i_arg, bprm);
        if (retval < 0) return retval;
        bprm->argc++;
    }
    retval = copy_strings_kernel(1, &i_name, bprm);
    if (retval) return retval;
    bprm->argc++;
    bprm->interp = interp;
内核只是把脚本中脚本之后的所有内容(删除最后空格)当作一个参数传递给脚本解释器,直观的说,所以内容在解释器中体现为argc参数的一个数值。
3、验证代码
harry:/home/tsecer/script #cat argdumper.c
#include <stdio.h>
int main(int argc, char * argv[])
{
        for (int i = 0; i < argc; i++)
        {
                printf("argv[%d] %s\n", i, argv[i]);
        }
        return 0;
}
harry:/home/tsecer/script #g++ argdumper.c -o argdumper
harry:/home/tsecer/script #cat script.awk
#!/home/tsecer/script/argdumper -F'|' -f
END {print "end"}
harry:/home/tsecer/script #./script.awk
argv[0] /home/tsecer/script/argdumper
argv[1] -F'|' -f
argv[2] ./script.awk
harry:/home/tsecer/script #
可以看到,所有内容作为一个参数,所以awk将field的分隔符理解为字符串('|' -f),之后内容为脚本内容,解析错误。
4、getopt
这也说明了Linux下很多工具都支持将选项写在一起的原因了,这个地方内核只支持一个参数,大家挤在一起节省空间。
harry:/home/tsecer/script #cat bashscript.sh
#!/bin/sh -ex
have not this command
can not get here
harry:/home/tsecer/script #./bashscript.sh
+ have not this command
./bashscript.sh: line 2: have: command not found
harry:/home/tsecer/script #
  评论这张
 
阅读(485)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017