Chinaunix首页 | 论坛 | 博客
  • 博客访问: 844315
  • 博文数量: 489
  • 博客积分: 475
  • 博客等级: 下士
  • 技术积分: 3087
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-08 16:28
文章分类

全部博文(489)

文章存档

2013年(7)

2012年(301)

2011年(181)

分类:

2012-04-29 17:35:44

   在上一节中我们知道3G卡设备驱动已经加载好了,并且和ttyUSB*已经绑定成功,意味着我们可以拨号了,和3G卡内部3G模块通信了,而我们知道3G模块通信是tty设备,通过串行设备,这里和3G卡的两种工作模式相吻合---moden模式.
  首先我们这里先给出3G工作原理图
  
我们万事俱备只欠东风了,就是pppd拨号了,当然pppd的源码自己可以去网上下载最新的,自己编译(这里我们只说linux环境的).安装好pppd程序后,我们运行pppd拨号,当然在拨号前我们需要配置下我们的pppd才能完美的工作,才能真正为我们对于的tty驱动和3g驱动运作起来.
 主要看pppd运行参数文件/etc/ppp/options(这个参数文件名字,看开发者自己是可以修改的)该文件指定pppd运行的参数,若运行pppd时通过命令行指定参数同时出现时,则选择/etc/ppp/options中的配置.下面我们就看相关的参数:
#tty Options
/dev/ttyS1               #这里可以将/dev/ttyS1注释,然后使用命令行pppd /dev/ttyS1 运行来指定;
115200                   #传输速率
lock                     #创建一个锁定文件,其他程序在发现存在这个文件后,就能得知相应的串口已经被使用。
modem                    #modem设备
crtscts                  #硬件流控为,无硬件流控为nocrtscts
#login
nodetach                 #若指定updetach则拨号成功后放入后台运行,若为nodetach,则在前台执行
debug                    #如果需要加入调试信息,就加入参数debug
#auth
require-pap
refuse-chap
name na_admin
connect "/usr/sbin/chat -v -E -V -f /etc/ppp/pstn-start-chat"
disconnect 'chat -s -v -f disconnect-chat'
...
当然这些信息我们都可以从pppd源码包的帮助文件中获取信息,比如readme等.
# 源码ppp-2.4.5/pppd/tty.c中的函数connect_tty()将会执行这个脚本。主要是对客户端的拨号的应答。
connect "/usr/sbin/chat -v -E -V -f /etc/ppp/pstn-start-chat"
 配置信息中我只说明/dev/ttyS1 这个tty名字是我们自己设定,看我们3g驱动和哪一个tty设备绑定到哪一个作为通信用.电信或者联通(默认华为3G卡,evdo)则为ttyUSB0,因为我们在3G卡驱动识别过程中会见到3g  attach ttyUSB*等信息. 还有就是在pppd运行开始,要初始化tty. 就是调用tty_init(). 
下面给出pppd相关代码:
    /*
     * Initialize each protocol.
     */
    for (i = 0; (protp = protocols[i]) != NULL; ++i)
        (*protp->init)(0);

    /*
     * Initialize the default channel.
     */
    tty_init();
展开tty_init():
void tty_init()
{
    add_notifier(&pidchange, maybe_relock, 0);
    the_channel = &tty_channel;
    xmit_accm[3] = 0x60000000;
}
我们看到里面the_channel = &tty_channel;的操作,struct channel *the_channel;(main.c中)是一个全局变量.
/*
 * This struct contains pointers to a set of procedures for
 * doing operations on a "channel".  A channel provides a way
 * to send and receive PPP packets - the canonical example is
 * a serial port device in PPP line discipline (or equivalently
 * with PPP STREAMS modules pushed onto it).
 */
struct channel {
/* set of options for this channel */
option_t *options;
/* find and process a per-channel options file */
void (*process_extra_options) __P((void));
/* check all the options that have been given */
void (*check_options) __P((void));
/* get the channel ready to do PPP, return a file descriptor */
int  (*connect) __P((void));
/* we're finished with the channel */
void (*disconnect) __P((void));
/* put the channel into PPP `mode' */
int  (*establish_ppp) __P((int));
/* take the channel out of PPP `mode', restore loopback if demand */
void (*disestablish_ppp) __P((int));
/* set the transmit-side PPP parameters of the channel */
void (*send_config) __P((int, u_int32_t, int, int));
/* set the receive-side PPP parameters of the channel */
void (*recv_config) __P((int, u_int32_t, int, int));
/* cleanup on error or normal exit */
void (*cleanup) __P((void));
/* close the device, called in children after fork */
void (*close) __P((void));
};

extern struct channel *the_channel;
在ppp.h中定义,并声明.
下面看看如何初始化的
struct channel tty_channel = {
tty_options,
&tty_process_extra_options,
&tty_check_options,
&connect_tty,
&disconnect_tty,
&tty_establish_ppp,
&tty_disestablish_ppp,
&tty_do_send_config,
&tty_recv_config,
&cleanup_tty,
&tty_close_fds
};
同理这里我们只关注&connect_tty, 这个初始化在tty.c中.
/*
 * connect_tty - get the serial port ready to start doing PPP.
 * That is, open the serial port, set its speed and mode, and run
 * the connector and/or welcomer.
 */
int connect_tty()
{
char *connector;
int fdflags;
#ifndef __linux__
struct stat statbuf;
#endif
char numbuf[16];

/*
* Get a pty master/slave pair if the pty, notty, socket,
* or record options were specified.
*/
strlcpy(ppp_devnam, devnam, sizeof(ppp_devnam));
pty_master = -1;
pty_slave = -1;
real_ttyfd = -1;
if (using_pty || record_file != NULL) {
if (!get_pty(&pty_master, &pty_slave, ppp_devnam, uid)) {
error("Couldn't allocate pseudo-tty");
status = EXIT_FATAL_ERROR;
return -1;
}
set_up_tty(pty_slave, 1);
}

/*
* Lock the device if we've been asked to.
*/
status = EXIT_LOCK_FAILED;
if (lockflag && !privopen) {
if (lock(devnam) < 0)
goto errret;
locked = 1;
}

/*
* Open the serial device and set it up to be the ppp interface.
* First we open it in non-blocking mode so we can set the
* various termios flags appropriately.  If we aren't dialling
* out and we want to use the modem lines, we reopen it later
* in order to wait for the carrier detect signal from the modem.
*/ 
got_sigterm = 0;
connector = doing_callback? callback_script: connect_script;
if (devnam[0] != 0) {
for (;;) {
/* If the user specified the device name, become the
  user before opening it. */
int err, prio;
 
     ...
这里我们要特别主要粗体注释部分.  就是在这里我们的ppp和tty联系了起来,才真正work. 当然还加载了前面的脚本文件,来获取必要的配置信息.
     tty_init()完成后,就是来和ppp内核协议通信来建立必要的ppp网络接口,以供上层应用程序用
     /*
     * If we're doing dial-on-demand, set up the interface now.
     */
    if (demand) {
/*
 * Open the loopback channel and set it up to be the ppp interface.
 */
fd_loop = open_ppp_loopback();
set_ifunit(1);
/*
 * Configure the interface and mark it up, etc.
 */
demand_conf();
    }
     在open_ppp_loopback()这个函数中调用 make_ppp_unit()建立ppp0接口(当然这个接口名我们也可以自定义)  
  /*
 * make_ppp_unit - make a new ppp unit for ppp_dev_fd.
 * Assumes new_style_driver.
 */
static int make_ppp_unit()
{
int x, flags;

if (ppp_dev_fd >= 0) {
dbglog("in make_ppp_unit, already had /dev/ppp open?");
close(ppp_dev_fd);
}
ppp_dev_fd = open("/dev/ppp", O_RDWR);
if (ppp_dev_fd < 0)
fatal("Couldn't open /dev/ppp: %m");
flags = fcntl(ppp_dev_fd, F_GETFL);
if (flags == -1
   || fcntl(ppp_dev_fd, F_SETFL, flags | O_NONBLOCK) == -1)
warn("Couldn't set /dev/ppp to nonblock: %m");

ifunit = req_unit;
x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
if (x < 0 && req_unit >= 0 && errno == EEXIST) {
warn("Couldn't allocate PPP unit %d as it is already in use", req_unit);
ifunit = -1;
x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
}
if (x < 0)
error("Couldn't create new ppp unit: %m");
return x;
}
   这里我们看到open函数打开了/dev/ppp ,然后发送一个ioctl给ppp协议
  在ppp协议里
  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
};

 ppp_ioctl调用ppp_unattached_ioctl(),在这个函数里
  switch (cmd) {
case PPPIOCNEWUNIT:
/* Create a new ppp unit */
if (copy_from_user(&req, p, sizeof req))
break;
ppp = ppp_create_interface(req.unit, &err, req.ifname);
ppp_create_interface来真正创建这个接口,以供将来应用程序使用.
   既然tty和ppp都初始化了,也建立了某种联系,并且能一起协同工作了,那么我们还犹豫什么?
  当然是start_link了
  start_link(0);
  在这个函数里我们才真正调用我们上面长篇大论的connect_tty().
  建立连接的过程分两个阶段:LCP,PCAP.
  pppd建立连接过程中chat辅助程序起到不可磨灭的作用.
  pppd运行后作为一个守护进程来运行,它必然有监控程序,而这个就是handle_events.
  有人认为我这里说这么多是扯淡,我感觉不是,为什么呢,虽然我们一般人直接用pppd拨号就可以,但是很多人并不知道为什么,为什么就把pppd、ppp协议、tty、3G模块联系起来的.这里分析下我想功夫不会是白费的.如果一旦出现问题,那么我们很容易定位问题的所在,知其然还要知其所以然.以后我们应该讲讲ppp相关的数据包收发流程.

阅读(800) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~