cyliu
论坛版主
论坛版主
  • 注册日期2003-06-13
  • 最后登录2014-04-11
  • 粉丝5
  • 关注0
  • 积分1238分
  • 威望2531点
  • 贡献值0点
  • 好评度577点
  • 原创分14分
  • 专家分10分
阅读:4020回复:1

linux2.6内核ppp分析(其中思想可以供重定向设计借鉴-通过文件操作访问局域网)

楼主#
更多 发布于:2007-03-24 13:25
  linux2.6内核ppp分析

1 简介

ppp协议(点到点协议),在拨号网络中应用比较广泛,逐渐在替代slip协议。

ppp数据包格式为:| 协议码 | 载荷     |填充符

ppp主要有四类协议码:

    1 0x0001 - 0x3fff  网络层协议(ipv4,ipv6,ipx,appletalk)
    
    2 0x4001 - 0x7fff  无网络层协议参与的小载荷量传输(低整流量)
    
    3 0x8001 - 0xbfff  用于配置网络层的子协议(网络控制协议,如ipcp)
    
    4 0xc001 - 0xffff  用于建立ppp连接的子协议(链路层控制协议,如lcp,pap,chap)
    
2 ppp协议实现的结构

    网络应用程序
    
    ip/tcp协议栈
    
    ppp网络协议 -|
                 |—— ppp内核实现
    ppp连线规程 -|
    
    调制解调器
    
    pppd 是应用进程,通过子符设备和ppp内核通信。
    
3 ppp收发数据过程

    a 发送数据
    
        ppp 协议发送数据有两种途径:一种是网络协议栈发送 ,另一种是pppd直接发送控制协议数据
        
    b 接收数据
    
        ppp连线规程解收到数据后,根据数据类型:如果是协商协议数据,则放到子符设备队列等待pppd读取;如果是网络层数据,则调用netif_rx入网络栈。
        
4 代码分析

a 初始化ppp设备,ppp字符主设备号为108

static struct file_operations ppp_device_fops = {
    .owner        = THIS_MODULE,
    .read        = ppp_read,
    .write        = ppp_write,
    .poll        = ppp_poll,
    .ioctl        = ppp_ioctl,
    .open        = ppp_open,
    .release    = ppp_release
};

#define PPP_MAJOR    108

static int __init ppp_init(void)
{
    int err;

    /* 注册字符设备,设备名称是ppp */
    err = register_chrdev(PPP_MAJOR, "ppp", &ppp_device_fops);
    if (!err) {
        /* 创建udev */
        ppp_class = class_create(THIS_MODULE, "ppp");
        if (IS_ERR(ppp_class)) {
            err = PTR_ERR(ppp_class);
            goto out_chrdev;
        }
        class_device_create(ppp_class, NULL, MKDEV(PPP_MAJOR, 0), NULL, "ppp");
    }

out:
    if (err)
        printk(KERN_ERR "failed to register PPP device (%d)\n", err);
    return err;

out_chrdev:
    unregister_chrdev(PPP_MAJOR, "ppp");
    goto out;
}

class_create用途是做什么的?

从linux内核2.6的某个版本之后,devfs不复存在,udev成为devfs的替代,udev是应用层的东东
加入对udev的支持很简单,在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用class_device_create创建对应的设备。大致用法如下:

    struct class *myclass = class_create(THIS_MODULE, "my_device_driver");
    class_device_create(myclass, NULL, MKDEV(major_num, 0), NULL, "my_device");

这样的module被加载时,udev daemon就会自动在/dev下创建my_device设备文件

 /* 字符设备ppp的file结构:
  * ---------------------
  * |     ....          |
  *----------------------
  * |     f_op          |
  *----------------------
  * |     ....          |
  *----------------------
  * |     private_data  |----------> struct ppp_file
  *----------------------
 
struct ppp_file {
    enum {
        INTERFACE=1, CHANNEL
    }        kind;
    struct sk_buff_head xq;        /* pppd transmit queue */
    struct sk_buff_head rq;        /* receive queue for pppd */
    wait_queue_head_t rwait;    /* for poll on reading /dev/ppp */
    atomic_t    refcnt;        /* # refs (incl /dev/ppp attached) */
    int        hdrlen;        /* space to leave for headers */
    int        index;        /* interface unit / channel number */
    int        dead;        /* unit/channel has been shut down */
};    

struct ppp {
    struct ppp_file    file;        /* stuff for read/write/poll 0 */
    struct file    *owner;        /* file that owns this unit 48 */
    ...
};


struct channel {
    struct ppp_file    file;        /* stuff for read/write/poll */
    struct list_head list;        /* link in all/new_channels list */
    struct ppp_channel *chan;    /* public channel data structure */
    struct rw_semaphore chan_sem;    /* protects `chan' during chan ioctl */
    spinlock_t    downl;        /* protects `chan', file.xq dequeue */
    struct ppp    *ppp;        /* ppp unit we're connected to */
    struct list_head clist;        /* link in list of channels per unit */
    rwlock_t    upl;        /* protects `ppp' */
        ...
};

struct ppp_channel {
    void        *private;    /* channel private data */
    struct ppp_channel_ops *ops;    /* operations for this channel */
    int        mtu;        /* max transmit packet size */
    int        hdrlen;        /* amount of headroom channel needs */
    void        *ppp;        /* opaque to channel */
    /* the following are not used at present */
    int        speed;        /* transfer rate (bytes/second) */
    int        latency;    /* overhead time in milliseconds */
};

static int ppp_open(struct net_device *dev)
{
    //分配hdlc设备对象
    hdlc_device *hdlc = dev_to_hdlc(dev);
    void *old_ioctl;
    int result;

    dev->priv = &hdlc->state.ppp.syncppp_ptr;
    hdlc->state.ppp.syncppp_ptr = &hdlc->state.ppp.pppdev;
    hdlc->state.ppp.pppdev.dev = dev;

    old_ioctl = dev->do_ioctl;
    hdlc->state.ppp.old_change_mtu = dev->change_mtu;
    sppp_attach(&hdlc->state.ppp.pppdev);
    /* sppp_attach nukes them. We don't need syncppp's ioctl */
    dev->do_ioctl = old_ioctl;
    hdlc->state.ppp.pppdev.sppp.pp_flags &= ~PP_CISCO;
    dev->type = ARPHRD_PPP;
    result = sppp_open(dev);
    if (result) {
        sppp_detach(dev);
        return result;
    }

    return 0;
}

static ssize_t ppp_read(struct file *file, char __user *buf,
            size_t count, loff_t *ppos)
{
    struct ppp_file *pf = file->private_data;
    
    //如果没有数据,则在该队列上等待
    DECLARE_WAITQUEUE(wait, current);
    
    ssize_t ret;
    struct sk_buff *skb = NULL;

    ret = count;

    if (pf == 0)
        return -ENXIO;
    add_wait_queue(&pf->rwait, &wait);
    for (;;) {
        set_current_state(TASK_INTERRUPTIBLE);
        skb = skb_dequeue(&pf->rq);//获取数据
        if (skb)
            break;
            
        //队列中没有数据
        ret = 0;
        if (pf->dead)
            break;
        if (pf->kind == INTERFACE) { //网络设备
            /*
             * Return 0 (EOF) on an interface that has no
             * channels connected, unless it is looping
             * network traffic (demand mode).
             */
            struct ppp *ppp = PF_TO_PPP(pf);
            if (ppp->n_channels == 0
                && (ppp->flags & SC_LOOP_TRAFFIC) == 0)
                break;
        }
        ret = -EAGAIN;
        if (file->f_flags & O_NONBLOCK)
            break;
        ret = -ERESTARTSYS;
        
        //设置pending ,挂起当前进程
        if (signal_pending(current))
            break;
            
        //重新调度入栈
        schedule();
    }
    set_current_state(TASK_RUNNING);
    remove_wait_queue(&pf->rwait, &wait);

    if (skb == 0)
        goto out;

    ret = -EOVERFLOW;
    if (skb->len > count)
        goto outf;
    ret = -EFAULT;
    
    //copy数据到用户空间
    if (copy_to_user(buf, skb->data, skb->len))
        goto outf;
    ret = skb->len;

 outf:
    kfree_skb(skb);
 out:
    return ret;
}

//发送数据
static ssize_t ppp_write(struct file *file, const char __user *buf,
             size_t count, loff_t *ppos)
{
    struct ppp_file *pf = file->private_data;
    struct sk_buff *skb;
    ssize_t ret;

    if (pf == 0)
        return -ENXIO;
    ret = -ENOMEM;
    
    //分配skb
    skb = alloc_skb(count + pf->hdrlen, GFP_KERNEL);
    if (skb == 0)
        goto out;
    skb_reserve(skb, pf->hdrlen);
    ret = -EFAULT;
    
    //从用户空间copy数据
    if (copy_from_user(skb_put(skb, count), buf, count)) {
        kfree_skb(skb);
        goto out;
    }

    skb_queue_tail(&pf->xq, skb);

    switch (pf->kind) {
    case INTERFACE:
        ppp_xmit_process(PF_TO_PPP(pf)); //通过网络接口发送
        break;
    case CHANNEL:
        ppp_channel_push(PF_TO_CHANNEL(pf));//通过规程接口发送
        break;
    }

    ret = count;

 out:
    return ret;
}

<待续>
走走看看开源好 Solaris vs Linux
strpic
驱动小牛
驱动小牛
  • 注册日期2006-11-01
  • 最后登录2016-01-09
  • 粉丝0
  • 关注0
  • 积分10分
  • 威望238点
  • 贡献值0点
  • 好评度156点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2007-04-19 23:23
好文章
游客

返回顶部