五.線路規(guī)程內(nèi)核代碼底層的物理驅(qū)動程序和tty驅(qū)動程序負責(zé)從硬件上收發(fā)數(shù)據(jù),而線路規(guī)程則負責(zé)處理這些數(shù)據(jù),并在用戶空間和內(nèi)核空間知覺傳遞數(shù)據(jù)。打開串行端口時系統(tǒng)默認的線路規(guī)程是N_TTY,它實現(xiàn)終端I/O處理。線路規(guī)程也實現(xiàn)通過串行傳輸協(xié)議實現(xiàn)的網(wǎng)絡(luò)接口,PPP(N_PPP),SLIP(串行線路網(wǎng)際協(xié)議)(N_SLIP),紅外數(shù)據(jù)(N_IRDA),藍牙主機控制接口(N_HCI)。
本文引用地址:http://cafeforensic.com/article/201611/319916.htm
我們在TTY層uart_register_driver函數(shù)里初始化termios的時候用到tty_std_termios,這個是線路的原始設(shè)置,具體定義如下
struct ktermios tty_std_termios = {
.c_iflag = ICRNL | IXON, //輸入標志
.c_oflag = OPOST | ONLCR, //輸出標志
.c_cflag = B38400 | CS8 | CREAD | HUPCL, //控制標志
.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
ECHOCTL | ECHOKE | IEXTEN, //本地標志
.c_cc = INIT_C_CC, //字符控制
.c_ispeed = 38400, //輸入速率
.c_ospeed = 38400 //輸出速率
};
如果需要對線路原始設(shè)置的部分加以修改,則可以添加其他操作。主要分為內(nèi)核空間修改線路規(guī)程和用戶空間修改線路規(guī)程兩個途徑。內(nèi)核空間修改線路規(guī)程很簡單,只需要對需要修改項進行重新賦值就行了,對于用戶空間修改線路規(guī)程我們來講解下。
假如用戶空間程序打開和觸摸控制器相連的串行端口時,N_TCH將被綁定到底層的串行驅(qū)動程序,但假如你想編寫程序清空觸摸控制器接收的所有原始數(shù)據(jù)而不處理它,那你就需要修改線路規(guī)程為N_TTY并清空所有接收的數(shù)據(jù)的程序。用戶空間修改線程代碼如下
fd=open(“/dev/ttys0”,O_RDONLY|O_NOCTTY);
ldisc=N_TTY;
ioctl(fd,TIOCSETD,&ldisc);
好了,前面我們從應(yīng)用角度分析了線路規(guī)程的設(shè)置,現(xiàn)在我們從理論角度,深度剖析下線路規(guī)程是怎么實現(xiàn)的吧。
在TTY層我們講過TTY層的uart_register_driver和uart_register_port最終調(diào)用線路規(guī)程的tty_register_driver和tty_register_device。而tty_register_driver和tty_register_device的實現(xiàn)在線路規(guī)程中tty_io.c中實現(xiàn)的,我們可以打開tty_io.c這個文件。
首先我們看tty_init函數(shù),在tty_init函數(shù)中執(zhí)行了cdev_init(&tty_cdev, &tty_fops)一行代碼,說明向內(nèi)核中添加了一個cdev設(shè)備,我們跟蹤tty_fops。
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = tty_write,
.poll = tty_poll,
.unlocked_ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
};
這個結(jié)構(gòu)體我們很熟悉,在字符設(shè)備中,我們就是使用的這個結(jié)構(gòu)體吧。那說明我們用戶進行open,read,write,ioctl等對串口操作時,第一步調(diào)用就是這里的open,read,write,ioctl。那么我們就看看怎么由這里的open,read,write,ioctl跟TTY層,UART層的open,read,write,ioctl相聯(lián)系的。
我們就來看看這個open吧
static int __tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty = NULL;
int noctty, retval;
struct tty_driver *driver;
int index;
dev_t device = inode->i_rdev; //獲取目標設(shè)備的設(shè)備號
unsigned saved_flags = filp->f_flags;
nonseekable_open(inode, filp);
retry_open:
noctty = filp->f_flags & O_NOCTTY;
index = -1;
retval = 0;
mutex_lock(&tty_mutex);
if (device == MKDEV(TTYAUX_MAJOR, 0)) { //當(dāng)前進程的控制終端,/dev/tty
tty = get_current_tty();
if (!tty) { //該進程還沒有控制終端
mutex_unlock(&tty_mutex);
return -ENXIO;
}
driver = tty_driver_kref_get(tty->driver); //如果打開的確實是控制終端的處理
index = tty->index;
filp->f_flags |= O_NONBLOCK;
tty_kref_put(tty);
goto got_driver;
}
#ifdef CONFIG_VT
if (device == MKDEV(TTY_MAJOR, 0)) { //當(dāng)前虛擬控制臺,/dev/tty0
extern struct tty_driver *console_driver;
driver = tty_driver_kref_get(console_driver);
index = fg_console; // fg_console表示當(dāng)前的前臺控制臺
noctty = 1; //因為虛擬控制臺原來就打開,故置位
goto got_driver;
}
#endif
if (device == MKDEV(TTYAUX_MAJOR, 1)) { //用于外接的控制臺,/dev/console
struct tty_driver *console_driver = console_device(&index);
if (console_driver) {
driver = tty_driver_kref_get(console_driver);
if (driver) {
filp->f_flags |= O_NONBLOCK;
noctty = 1;
goto got_driver;
}
}
mutex_unlock(&tty_mutex);
return -ENODEV;
}
driver = get_tty_driver(device, &index);
if (!driver) {
mutex_unlock(&tty_mutex);
return -ENODEV;
}
got_driver:
if (!tty) {
//檢查我們是否重復(fù)打開一個已經(jīng)存在的tty
tty = tty_driver_lookup_tty(driver, inode, index);
if (IS_ERR(tty)) {
mutex_unlock(&tty_mutex);
return PTR_ERR(tty);
}
}
if (tty) {
retval = tty_reopen(tty); //重新打開
if (retval)
tty = ERR_PTR(retval);
} else
tty = tty_init_dev(driver, index, 0); //初始化,為需要打開的終端建立tty_struct結(jié)構(gòu)體
mutex_unlock(&tty_mutex);
tty_driver_kref_put(driver);
if (IS_ERR(tty))
return PTR_ERR(tty);
filp->private_data = tty; //設(shè)置私有數(shù)據(jù)
file_move(filp, &tty->tty_files);
check_tty_count(tty, "tty_open");
if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
tty->driver->subtype == PTY_TYPE_MASTER)
noctty = 1;
#ifdef TTY_DEBUG_HANGUP
printk(KERN_DEBUG "opening %s...", tty->name);
#endif
if (!retval) {
if (tty->ops->open)
retval = tty->ops->open(tty, filp); //調(diào)用tty_operations下的open函數(shù)
else
retval = -ENODEV;
}
filp->f_flags = saved_flags;
if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&
!capable(CAP_SYS_ADMIN))
retval = -EBUSY;
if (retval) {
#ifdef TTY_DEBUG_HANGUP
printk(KERN_DEBUG "error %d in opening %s...", retval,
tty->name);
#endif
tty_release_dev(filp);
if (retval != -ERESTARTSYS)
return retval;
if (signal_pending(current))
return retval;
schedule();
//需要復(fù)位f_op,以防掛起
if (filp->f_op == &hung_up_tty_fops)
filp->f_op = &tty_fops;
goto retry_open;
}
mutex_lock(&tty_mutex);
spin_lock_irq(¤t->sighand->siglock);
if (!noctty &&
current->signal->leader &&
!current->signal->tty &&
tty->session == NULL)
__proc_set_tty(current, tty);
spin_unlock_irq(¤t->sighand->siglock);
mutex_unlock(&tty_mutex);
return 0;
}
在上面這個open函數(shù)中,我們主要涉及為需要打開的終端建立tty_struct結(jié)構(gòu)體而執(zhí)行的一條代碼tty_init_dev(driver, index, 0),同時看到了怎么調(diào)用tty_operations下的open函數(shù)。在此我們好好看看tty_init_dev(driver, index, 0)的內(nèi)幕吧。
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,
int first_ok)
{
struct tty_struct *tty;
int retval;
//檢查是否pty被多次打開
if (driver->subtype == PTY_TYPE_MASTER &&
(driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok)
return ERR_PTR(-EIO);
if (!try_module_get(driver->owner))
return ERR_PTR(-ENODEV);
tty = alloc_tty_struct(); //分配tty_struct結(jié)構(gòu)體
if (!tty)
goto fail_no_mem;
initialize_tty_struct(tty, driver, idx); //初始化tty_struct結(jié)構(gòu)體
retval = tty_driver_install_tty(driver, tty);
if (retval < 0) {
free_tty_struct(tty);
module_put(driver->owner);
return ERR_PTR(retval);
}
retval = tty_ldisc_setup(tty, tty->link); //調(diào)用ldisc下open
if (retval)
goto release_mem_out;
return tty;
fail_no_mem:
module_put(driver->owner);
return ERR_PTR(-ENOMEM);
release_mem_out:
if (printk_ratelimit())
printk(KERN_INFO "tty_init_dev: ldisc open failed, "
"clearing slot %dn", idx);
release_tty(tty, idx);
return ERR_PTR(retval);
}
我們繼續(xù)跟蹤tty_init_dev中的initialize_tty_struct(tty, driver, idx)函數(shù)實現(xiàn)吧
void initialize_tty_struct(struct tty_struct *tty,
struct tty_driver *driver, int idx)
{
memset(tty, 0, sizeof(struct tty_struct));
kref_init(&tty->kref);
tty->magic = TTY_MAGIC;
tty_ldisc_init(tty); // tty_ldisc的初始化,
tty->session = NULL;
tty->pgrp = NULL;
tty->overrun_time = jiffies;
tty->buf.head = tty->buf.tail = NULL;
tty_buffer_init(tty);
mutex_init(&tty->termios_mutex);
mutex_init(&tty->ldisc_mutex);
init_waitqueue_head(&tty->write_wait);
init_waitqueue_head(&tty->read_wait);
INIT_WORK(&tty->hangup_work, do_tty_hangup);
mutex_init(&tty->atomic_read_lock);
mutex_init(&tty->atomic_write_lock);
mutex_init(&tty->output_lock);
mutex_init(&tty->echo_lock);
spin_lock_init(&tty->read_lock);
spin_lock_init(&tty->ctrl_lock);
INIT_LIST_HEAD(&tty->tty_files);
INIT_WORK(&tty->SAK_work, do_SAK_work);
tty->driver = driver;
tty->ops = driver->ops;
tty->index = idx;
tty_line_name(driver, idx, tty->name);
}
我們繼續(xù)跟蹤initialize_tty_struct函數(shù)中的tty_ldisc_init(tty)函數(shù)
void tty_ldisc_init(struct tty_struct *tty)
{
struct tty_ldisc *ld = tty_ldisc_get(N_TTY); //設(shè)置線路規(guī)程N_TTY
if (IS_ERR(ld))
panic("n_tty: init_tty");
tty_ldisc_assign(tty, ld);
}
在tty_ldisc_init里,我們終于找到了N_TTY,這是默認的線路規(guī)程。
繼續(xù)看tty_init_dev,我們發(fā)現(xiàn)retval = tty_ldisc_setup(tty, tty->link);繼續(xù)跟蹤
int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)
{
struct tty_ldisc *ld = tty->ldisc;
int retval;
retval = tty_ldisc_open(tty, ld);
if (retval)
return retval;
if (o_tty) {
retval = tty_ldisc_open(o_tty, o_tty->ldisc);
if (retval) {
tty_ldisc_close(tty, ld);
return retval;
}
tty_ldisc_enable(o_tty);
}
tty_ldisc_enable(tty);
return 0;
}
然后我們跟蹤tty_ldisc_setup函數(shù)中的tty_ldisc_open函數(shù)
static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)
{
WARN_ON(test_and_set_bit(TTY_LDISC_OPEN, &tty->flags));
if (ld->ops->open)
return ld->ops->open(tty); //打開ldisc下的open,進行鏈路的初始化
return 0;
}
在tty_ldisc_open這里已經(jīng)通過相應(yīng)tty_ldisc結(jié)構(gòu)所提供的函數(shù)指針調(diào)用了與鏈路規(guī)則有關(guān)的open操作。
前面tty_open函數(shù)中也有個open調(diào)用,這是為什么呢?因為具體的終端類型也可能有需要在打開文件時加以調(diào)用的函數(shù)。對于用作控制臺的虛擬終端,其tty_driver數(shù)據(jù)結(jié)構(gòu)為console_driver,其open函數(shù)則為con_open()。
綜上,我們可以把tty_io.c看作是tty核心,然后tty核心里調(diào)用ldisc中的open,ldisc里的open調(diào)用tty層的open,tty層的open調(diào)用uart層的open,最終實現(xiàn)打開操作。
最后再次總結(jié)如下幾點:
其一,內(nèi)核中有一個鏈表tty_drivers,系統(tǒng)在初始化時,或者安裝某種終端設(shè)備的驅(qū)動模塊時,通過函數(shù)tty_register_driver()將各種終端設(shè)備的tty_driver結(jié)構(gòu)登記到這個鏈表中。每當(dāng)新打開一個終端設(shè)備時,就要根據(jù)其設(shè)備號通過函數(shù)get_tty_driver()在這個鏈表中找到的tty_driver結(jié)構(gòu),并把它復(fù)制到具體的tty_struct結(jié)構(gòu)體中。
其二,當(dāng)新創(chuàng)建一個tty_struct結(jié)構(gòu)時,就把相應(yīng)的tty_ldisc結(jié)構(gòu)復(fù)制到tty_struct結(jié)構(gòu)體中的這個成員中。
其三,另外內(nèi)核中的一個重要指針termios,這個數(shù)據(jù)結(jié)構(gòu)在某種程度上可以看作是對tty_ldisc結(jié)構(gòu)的補充,它規(guī)定了對接口上輸入和輸出的每個字符所作的處理以及傳輸?shù)乃俣?,即波特率?/p>
評論