前言
tty這個(gè)名稱源于電傳打字節(jié)的簡稱,在linux表示各種終端,終端通常都跟硬件相對(duì)應(yīng)。比如對(duì)應(yīng)于輸入設(shè)備鍵盤鼠標(biāo),輸出設(shè)備顯示器的控制終端和串口終端。也有對(duì)應(yīng)于不存在設(shè)備的pty驅(qū)動(dòng)。在如此眾多的終端模型之中,linux是怎么將它們統(tǒng)一建模的呢?這就是我們今天要討論的問題。
tty驅(qū)動(dòng)概貌
tty架構(gòu)如下所示:
如上圖所示,用戶空間主要是通過系統(tǒng)調(diào)用與tty core交互。tty core根據(jù)用空間操作的類型再選擇跟line discipline和tty driver交互。
例如,設(shè)置硬件的ioctl指令就直接交給tty_driver處理。read和write操作就會(huì)交給 line discipline處理。
Line discipline是線路規(guī)程的意思。正如它的名字一樣,它表示的是這條終端”線程”的輸入與輸出規(guī)范設(shè)置。主要用來進(jìn)行輸入/輸出數(shù)據(jù)的預(yù)處理。
處理之后,就會(huì)將數(shù)據(jù)交給tty driver ,它將字符轉(zhuǎn)換成終端可以理解的字串。將其傳給終端設(shè)備。
值得注意的是,這個(gè)架構(gòu)沒有為tty driver 提供read操作。也就是說tty core 和line discipline都沒有辦法從tty driver里直接讀終端信息。這是因?yàn)閠ty driver對(duì)應(yīng)的hardware并不一定是輸入數(shù)據(jù)和輸出 數(shù)據(jù)的共同負(fù)載者。
例如控制終端,輸出設(shè)備是顯示器,輸入設(shè)備是鍵盤?;谶@樣的原理。在line discipline中有一個(gè)輸入緩存區(qū),并提供了一個(gè)名叫receive_buf()的接口函數(shù)。對(duì)應(yīng)的終端設(shè)備只要調(diào)用line discipine的receiver_buf函數(shù),將數(shù)據(jù)寫入到輸入緩存區(qū)就可以了。如果一個(gè)設(shè)備同時(shí)是輸入設(shè)備又是輸出設(shè)備。那在設(shè)備的中斷處理中調(diào)用receive_buf()將數(shù)據(jù)寫入即可.
tty驅(qū)動(dòng)接口分析
tty_init()
/* *Ok,nowwecaninitializetherestofthettydevicesandcancount *onmemoryallocations,interruptsetc.. */ int__inittty_init(void) { tty_sysctl_init(); cdev_init(&tty_cdev,&tty_fops); if(cdev_add(&tty_cdev,MKDEV(TTYAUX_MAJOR,0),1)|| register_chrdev_region(MKDEV(TTYAUX_MAJOR,0),1,"/dev/tty")0) ??panic("Couldn't?register?/dev/tty?driver "); ?device_create(tty_class,?NULL,?MKDEV(TTYAUX_MAJOR,?0),?NULL,?"tty"); ?cdev_init(&console_cdev,?&console_fops); ?if?(cdev_add(&console_cdev,?MKDEV(TTYAUX_MAJOR,?1),?1)?|| ?????register_chrdev_region(MKDEV(TTYAUX_MAJOR,?1),?1,?"/dev/console")?0) ??panic("Couldn't?register?/dev/console?driver "); ?consdev?=?device_create_with_groups(tty_class,?NULL, ?????????MKDEV(TTYAUX_MAJOR,?1),?NULL, ?????????cons_dev_groups,?"console"); ?if?(IS_ERR(consdev)) ??consdev?=?NULL; #ifdef?CONFIG_VT ?vty_init(&console_fops); #endif ?return?0; }
tty_init主要做了以下工作:
初始化 tty 子系統(tǒng)的 sysctl 相關(guān)設(shè)置,包括注冊(cè) sysctl 參數(shù)、創(chuàng)建 sysctl 目錄等。
初始化 tty 設(shè)備的字符設(shè)備對(duì)象,并將其與 tty 設(shè)備操作函數(shù) tty_fops 綁定。同時(shí),創(chuàng)建一個(gè)名為 "tty" 的 tty 設(shè)備節(jié)點(diǎn),并將其設(shè)備號(hào)設(shè)置為 MKDEV(TTYAUX_MAJOR, 0)。
初始化控制臺(tái)設(shè)備的字符設(shè)備對(duì)象,并將其添加到字符設(shè)備系統(tǒng)中。同時(shí),創(chuàng)建一個(gè)名為 "console" 的控制臺(tái)設(shè)備節(jié)點(diǎn),并將其設(shè)備號(hào)設(shè)置為 MKDEV(TTYAUX_MAJOR, 1)。該控制臺(tái)設(shè)備節(jié)點(diǎn)還將在 sysfs 中創(chuàng)建一個(gè)名為 "console" 的目錄,并在該目錄下創(chuàng)建多個(gè)屬性文件,用于控制控制臺(tái)的一些屬性。
如果內(nèi)核支持虛擬終端,則初始化虛擬終端。
這里我們看到了熟悉的cdev_init(),device_create()之類的函數(shù),這正是字符設(shè)備的創(chuàng)建流程。因此,我們說串口驅(qū)動(dòng)也是一個(gè)字符設(shè)備驅(qū)動(dòng)。
而在serial8250_init()中,會(huì)調(diào)用platform_driver_register()去注冊(cè)serial8250_isa_driver,在設(shè)備樹節(jié)點(diǎn)和serial8250_isa_driver name匹配的時(shí)候,就會(huì)進(jìn)入probe流程。因此,也可以說串口驅(qū)動(dòng)是總線設(shè)備驅(qū)動(dòng)模型。
tty_alloc_driver
/*UseTTY_DRIVER_*flagsbelow*/ #definetty_alloc_driver(lines,flags) __tty_alloc_driver(lines,THIS_MODULE,flags)
__tty_alloc_driver()用于分配一個(gè) tty 驅(qū)動(dòng)程序的數(shù)據(jù)結(jié)構(gòu) struct tty_driver,并對(duì)其一些常用字段進(jìn)行初始化。
/** *__tty_alloc_driver--allocatettydriver *@lines:countoflinesthisdrivercanhandleatmost *@owner:modulewhichisrepsonsibleforthisdriver *@flags:someofTTY_DRIVER_*flags,willbesetindriver->flags * *Thisshouldnotbecalleddirectly,someoftheprovidedmacrosshouldbe *usedinstead.UseIS_ERRandfriendson@retval. */ structtty_driver*__tty_alloc_driver(unsignedintlines,structmodule*owner, unsignedlongflags) { structtty_driver*driver; unsignedintcdevs=1; interr; if(!lines||(flags&TTY_DRIVER_UNNUMBERED_NODE&&lines>1)) returnERR_PTR(-EINVAL); /*分配一個(gè)structtty_driver結(jié)構(gòu)體,并對(duì)其中的一些字段進(jìn)行初始化,包括num、owner、flags等*/ driver=kzalloc(sizeof(structtty_driver),GFP_KERNEL); if(!driver) returnERR_PTR(-ENOMEM); kref_init(&driver->kref); driver->magic=TTY_DRIVER_MAGIC; driver->num=lines; driver->owner=owner; driver->flags=flags; /*如果TTY_DRIVER_DEVPTS_MEM標(biāo)志位沒有被設(shè)置,那么函數(shù)會(huì)分配driver->ttys和driver->termios,否則不需要分配*/ if(!(flags&TTY_DRIVER_DEVPTS_MEM)){ driver->ttys=kcalloc(lines,sizeof(*driver->ttys), GFP_KERNEL); driver->termios=kcalloc(lines,sizeof(*driver->termios), GFP_KERNEL); if(!driver->ttys||!driver->termios){ err=-ENOMEM; gotoerr_free_all; } } /*如果TTY_DRIVER_DYNAMIC_ALLOC標(biāo)志位沒有被設(shè)置,那么函數(shù)會(huì)分配driver->ports,否則不需要分配*/ if(!(flags&TTY_DRIVER_DYNAMIC_ALLOC)){ driver->ports=kcalloc(lines,sizeof(*driver->ports), GFP_KERNEL); if(!driver->ports){ err=-ENOMEM; gotoerr_free_all; } cdevs=lines; } /*函數(shù)會(huì)根據(jù)lines的值分配相應(yīng)數(shù)量的driver->cdevs*/ driver->cdevs=kcalloc(cdevs,sizeof(*driver->cdevs),GFP_KERNEL); if(!driver->cdevs){ err=-ENOMEM; gotoerr_free_all; } returndriver; err_free_all: kfree(driver->ports); kfree(driver->ttys); kfree(driver->termios); kfree(driver->cdevs); kfree(driver); returnERR_PTR(err); }
tty_register_driver
tty_register_driver用于注冊(cè) tty 驅(qū)動(dòng)程序的,被 tty 驅(qū)動(dòng)程序調(diào)用以將自己注冊(cè)到內(nèi)核中。
/* *Calledbyattydrivertoregisteritself. */ inttty_register_driver(structtty_driver*driver) { interror; inti; dev_tdev; structdevice*d; /*確認(rèn)是否要內(nèi)核動(dòng)態(tài)分配主設(shè)備號(hào)*/ if(!driver->major){ /*函數(shù)調(diào)用alloc_chrdev_region函數(shù)來動(dòng)態(tài)分配主設(shè)備號(hào),并將分配的主設(shè)備號(hào)和次設(shè)備號(hào)保存在driver->major和driver->minor_start字段中*/ error=alloc_chrdev_region(&dev,driver->minor_start, driver->num,driver->name); if(!error){ driver->major=MAJOR(dev); driver->minor_start=MINOR(dev); } }else{ /*已經(jīng)預(yù)先分配了主設(shè)備號(hào),函數(shù)調(diào)用register_chrdev_region函數(shù)來注冊(cè)設(shè)備號(hào)*/ dev=MKDEV(driver->major,driver->minor_start); error=register_chrdev_region(dev,driver->num,driver->name); } if(error0) ??goto?err; ?/*判斷是否設(shè)置了?TTY_DRIVER_DYNAMIC_ALLOC?標(biāo)志位*/ ?if?(driver->flags&TTY_DRIVER_DYNAMIC_ALLOC){ /*需要?jiǎng)討B(tài)分配tty設(shè)備號(hào),函數(shù)調(diào)用tty_cdev_add函數(shù)來添加tty設(shè)備號(hào),并將每個(gè)tty設(shè)備的字符設(shè)備注冊(cè)到內(nèi)核中*/ error=tty_cdev_add(driver,dev,0,driver->num); if(error) gotoerr_unreg_char; } mutex_lock(&tty_mutex); /*將driver添加到鏈表tty_drivers中*/ list_add(&driver->tty_drivers,&tty_drivers); mutex_unlock(&tty_mutex); /*判斷TTY_DRIVER_DYNAMIC_DEV標(biāo)志位是否設(shè)置*/ if(!(driver->flags&TTY_DRIVER_DYNAMIC_DEV)){ for(i=0;inum;i++){ /*需要注冊(cè)固定的tty設(shè)備號(hào),函數(shù)在循環(huán)中調(diào)用tty_register_device函數(shù)來注冊(cè)每個(gè)tty設(shè)備號(hào),并將每個(gè)tty設(shè)備注冊(cè)到內(nèi)核中*/ d=tty_register_device(driver,i,NULL); if(IS_ERR(d)){ error=PTR_ERR(d); gotoerr_unreg_devs; } } } /*注冊(cè)/proc/tty/drivers目錄中的信息*/ proc_tty_register_driver(driver); /*將driver結(jié)構(gòu)體中的flags字段設(shè)置為TTY_DRIVER_INSTALLED,表示該驅(qū)動(dòng)程序已經(jīng)被成功注冊(cè)到內(nèi)核中*/ driver->flags|=TTY_DRIVER_INSTALLED; return0; err_unreg_devs: for(i--;i>=0;i--) tty_unregister_device(driver,i); mutex_lock(&tty_mutex); list_del(&driver->tty_drivers); mutex_unlock(&tty_mutex); err_unreg_char: unregister_chrdev_region(dev,driver->num); err: returnerror; }
tty_register_driver()函數(shù)操作比較簡單。就是為tty_driver創(chuàng)建字符設(shè)備。然后將字符設(shè)備的操作集指定為tty_fops。并且將tty_driver 掛載到tty_drivers鏈表中。這個(gè)鏈表中是以設(shè)備號(hào)為關(guān)鍵字找到對(duì)應(yīng)的driver。
特別的。如果沒有定義TTY_DRIVER_DYNAMIC_DEV。還會(huì)在sysfs中創(chuàng)建一個(gè)類設(shè)備。這樣主要是為了udev管理設(shè)備。
tty_unregister_device
tty_unregister_device用于注銷一個(gè) tty 設(shè)備。該函數(shù)的作用是銷毀設(shè)備節(jié)點(diǎn)和字符設(shè)備,以便于釋放與該 tty 設(shè)備相關(guān)的資源,例如內(nèi)存和設(shè)備文件等.
/** *tty_unregister_device-unregisterattydevice *@driver:thettydriverthatdescribesthettydevice *@index:theindexinthettydriverforthisttydevice * *Ifattydeviceisregisteredwithacalltotty_register_device()then *thisfunctionmustbecalledwhenthettydeviceisgone. * *Locking:?? */ voidtty_unregister_device(structtty_driver*driver,unsignedindex) { device_destroy(tty_class, MKDEV(driver->major,driver->minor_start)+index); if(!(driver->flags&TTY_DRIVER_DYNAMIC_ALLOC)){ cdev_del(driver->cdevs[index]); driver->cdevs[index]=NULL; } }
tty_unregister_device所做工作如下:
調(diào)用 device_destroy 函數(shù)來銷毀 tty 設(shè)備對(duì)應(yīng)的設(shè)備節(jié)點(diǎn)。接受兩個(gè)參數(shù):第一個(gè)參數(shù) tty_class 表示 tty 類,第二個(gè)參數(shù)是 tty 設(shè)備的設(shè)備號(hào),其中 MKDEV(driver->major, driver->minor_start) + index 表示 tty 設(shè)備的設(shè)備號(hào),driver->major 表示 tty 設(shè)備的主設(shè)備號(hào),driver->minor_start 表示 tty 設(shè)備的次設(shè)備號(hào)的起始值,index 表示 tty 設(shè)備的索引
如果該 tty 驅(qū)動(dòng)程序不是動(dòng)態(tài)分配的,則調(diào)用 cdev_del 函數(shù)來注銷該 tty 設(shè)備對(duì)應(yīng)的字符設(shè)備。
get_tty_driver
get_tty_driver作用是在用戶空間的應(yīng)用程序使用 tty 設(shè)備時(shí),獲取對(duì)應(yīng)的 tty 驅(qū)動(dòng)程序的信息。
/** *get_tty_driver-finddeviceofatty *@dev_t:deviceidentifier *@index:returnstheindexofthetty * *Thisroutinereturnsattydriverstructure,givenadevicenumber *andalsopassesbacktheindexnumber. * *Locking:callermustholdtty_mutex */ staticstructtty_driver*get_tty_driver(dev_tdevice,int*index) { structtty_driver*p; /**/ list_for_each_entry(p,&tty_drivers,tty_drivers){ dev_tbase=MKDEV(p->major,p->minor_start); if(device=base+p->num) continue; *index=device-base; returntty_driver_kref_get(p); } returnNULL; }
首先使用 list_for_each_entry 循環(huán)遍歷全局鏈表 tty_drivers,該鏈表中保存了所有已經(jīng)注冊(cè)的 tty 驅(qū)動(dòng)程序。對(duì)于每個(gè) tty 驅(qū)動(dòng)程序,函數(shù)將其設(shè)備號(hào)的起始值和結(jié)束值計(jì)算出來,如果給定設(shè)備號(hào)不在這個(gè)范圍內(nèi),則繼續(xù)遍歷下一個(gè) tty 驅(qū)動(dòng)程序。
如果給定設(shè)備號(hào)在某個(gè) tty 驅(qū)動(dòng)程序的范圍內(nèi),則計(jì)算出該設(shè)備號(hào)對(duì)應(yīng)的 tty 設(shè)備的索引值,并調(diào)用 tty_driver_kref_get 函數(shù)來獲取該 tty 驅(qū)動(dòng)程序的引用計(jì)數(shù)。函數(shù)返回該 tty 驅(qū)動(dòng)程序的結(jié)構(gòu)體指針,并將找到的 tty 設(shè)備的索引值保存到 index 參數(shù)中。
需要注意的是,函數(shù)在訪問全局鏈表 tty_drivers 時(shí),需要持有互斥鎖 tty_mutex。因?yàn)槎鄠€(gè)應(yīng)用程序可能同時(shí)訪問同一個(gè) tty 驅(qū)動(dòng)程序,如果沒有互斥鎖保護(hù),可能會(huì)導(dǎo)致并發(fā)問題。
tty_open
從注冊(cè)的過程可以看到,所有的操作都會(huì)對(duì)應(yīng)到tty_fops中。Open操作對(duì)應(yīng)的操作接口是tty_open(),用于打開一個(gè) tty 設(shè)備。函數(shù)的作用是在用戶空間的應(yīng)用程序使用 tty 設(shè)備時(shí),打開對(duì)應(yīng)的 tty 設(shè)備,并初始化相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。
/** *tty_open-openattydevice *@inode:inodeofdevicefile *@filp:filepointertotty * *tty_openandtty_releasekeepupthettycountthatcontainsthe *numberofopensdoneonatty.Wecannotusetheinode-count,as *differentinodesmightpointtothesametty. * *Open-countingisneededforptymasters,aswellasforkeeping *trackofseriallines:DTRisdroppedwhenthelastclosehappens. *(Thisisnotdonesolelythroughtty->count,now.-Ted1/27/92) * *Thetermiosstateofaptyisresetonfirstopensothat *settingsdon'tpersistacrossreuse. * *Locking:tty_mutexprotectstty,tty_lookup_driverandtty_init_dev. *tty->countshouldprotecttherest. *->siglockprotects->signal/->sighand * *Note:thetty_unlock/lockcaseswithoutarefareonlysafedueto *tty_mutex */ staticinttty_open(structinode*inode,structfile*filp) { structtty_struct*tty; intnoctty,retval; structtty_driver*driver=NULL; intindex; dev_tdevice=inode->i_rdev; unsignedsaved_flags=filp->f_flags; nonseekable_open(inode,filp); retry_open: /*分配一個(gè)tty結(jié)構(gòu)體*/ retval=tty_alloc_file(filp); if(retval) return-ENOMEM; /*檢查文件的標(biāo)志位,如果包含O_NOCTTY標(biāo)志,則禁止將該tty設(shè)備設(shè)置為控制終端*/ noctty=filp->f_flags&O_NOCTTY; index=-1; retval=0; /*嘗試打開當(dāng)前的tty設(shè)備*/ tty=tty_open_current_tty(device,filp); if(!tty){ mutex_lock(&tty_mutex); /*根據(jù)設(shè)備號(hào)來查找對(duì)應(yīng)的tty驅(qū)動(dòng)程序,并初始化該tty設(shè)備,將找到的tty驅(qū)動(dòng)程序保存到driver變量中*/ driver=tty_lookup_driver(device,filp,&noctty,&index); if(IS_ERR(driver)){ retval=PTR_ERR(driver); gotoerr_unlock; } /*checkwhetherwe'rereopeninganexistingtty*/ /*查找對(duì)應(yīng)的tty設(shè)備,并將找到的tty設(shè)備結(jié)構(gòu)體指針保存到tty變量中*/ tty=tty_driver_lookup_tty(driver,inode,index); if(IS_ERR(tty)){ retval=PTR_ERR(tty); gotoerr_unlock; } if(tty){ /*如果找到了該tty設(shè)備,則需要重新打開該tty設(shè)備*/ mutex_unlock(&tty_mutex); retval=tty_lock_interruptible(tty); tty_kref_put(tty);/*dropkreffromtty_driver_lookup_tty()*/ if(retval){ if(retval==-EINTR) retval=-ERESTARTSYS; gotoerr_unref; } retval=tty_reopen(tty); if(retval0)?{ ????tty_unlock(tty); ????tty?=?ERR_PTR(retval); ???} ??}?else?{?/*?Returns?with?the?tty_lock?held?for?now?*/ ????????????/*需要初始化該?tty?設(shè)備*/ ???tty?=?tty_init_dev(driver,?index); ????????????/*為該?tty?設(shè)備分配一個(gè)?tty?結(jié)構(gòu)體,并對(duì)其進(jìn)行初始化*/ ???mutex_unlock(&tty_mutex); ??} ??tty_driver_kref_put(driver); ?} ?if?(IS_ERR(tty))?{ ??retval?=?PTR_ERR(tty); ??if?(retval?!=?-EAGAIN?||?signal_pending(current)) ???goto?err_file; ??tty_free_file(filp); ??schedule(); ??goto?retry_open; ?} ?/*將該?tty?設(shè)備與文件結(jié)構(gòu)體相關(guān)聯(lián)*/ ?tty_add_file(tty,?filp); ?check_tty_count(tty,?__func__); ????/*如果該?tty?設(shè)備是一個(gè)偽終端主設(shè)備,則需要將?noctty?標(biāo)志設(shè)置為?1*/ ?if?(tty->driver->type==TTY_DRIVER_TYPE_PTY&& tty->driver->subtype==PTY_TYPE_MASTER) noctty=1; tty_debug_hangup(tty,"(ttycount=%d) ",tty->count); /*調(diào)用tty設(shè)備的open函數(shù)*/ if(tty->ops->open) retval=tty->ops->open(tty,filp); else retval=-ENODEV; filp->f_flags=saved_flags; if(retval){ tty_debug_hangup(tty,"error%d,releasing... ",retval); tty_unlock(tty);/*needtocalltty_releasewithoutBTM*/ tty_release(inode,filp); if(retval!=-ERESTARTSYS) returnretval; if(signal_pending(current)) returnretval; schedule(); /* *Needtoresetf_opincaseahanguphappened. */ if(tty_hung_up_p(filp)) filp->f_op=&tty_fops; gotoretry_open; } clear_bit(TTY_HUPPED,&tty->flags); read_lock(&tasklist_lock); spin_lock_irq(¤t->sighand->siglock); if(!noctty&& current->signal->leader&& !current->signal->tty&& tty->session==NULL){ /* *Don'tletaprocessthatonlyhaswriteaccesstothetty *obtaintheprivilegesassociatedwithhavingattyas *controllingterminal(beingabletoreopenitwithfull *accessthrough/dev/tty,beingabletoperformpushback). *Manydistributionssetthegroupofallttysto"tty"and *grantwrite-onlyaccesstoallterminalsforsetgidtty *binaries,whichshouldnotimplyfullprivilegesonallttys. * *Thiscouldtheoreticallybreakoldcodethatperformsopen() *onawrite-onlyfiledescriptor.Inthatcase,itmightbe *necessarytoalsopermitthisif *inode_permission(inode,MAY_READ)==0. */ if(filp->f_mode&FMODE_READ) __proc_set_tty(tty); } spin_unlock_irq(¤t->sighand->siglock); read_unlock(&tasklist_lock); tty_unlock(tty); return0; err_unlock: mutex_unlock(&tty_mutex); err_unref: /*afterlockstoavoiddeadlock*/ if(!IS_ERR_OR_NULL(driver)) tty_driver_kref_put(driver); err_file: tty_free_file(filp); returnretval; }
函數(shù)所作工作如下:
在打開 tty 設(shè)備時(shí),該函數(shù)會(huì)檢查文件的標(biāo)志位,如果包含 O_NOCTTY 標(biāo)志,則禁止將該 tty 設(shè)備設(shè)置為控制終端。這是因?yàn)槿绻粋€(gè)進(jìn)程打開一個(gè) tty 設(shè)備并將其設(shè)置為控制終端,其他進(jìn)程就無法再將該 tty 設(shè)備設(shè)置為控制終端,這可能會(huì)導(dǎo)致一些問題。
如果打開當(dāng)前的 tty 設(shè)備失敗,則需要根據(jù)設(shè)備號(hào)來查找對(duì)應(yīng)的 tty 驅(qū)動(dòng)程序,并初始化該 tty 設(shè)備。在查找 tty 驅(qū)動(dòng)程序時(shí),需要調(diào)用 tty_lookup_driver 函數(shù)來查找對(duì)應(yīng)的 tty 驅(qū)動(dòng)程序,并將找到的 tty 驅(qū)動(dòng)程序保存到 driver 變量中。如果找不到對(duì)應(yīng)的 tty 驅(qū)動(dòng)程序,則返回錯(cuò)誤碼。
如果找到了對(duì)應(yīng)的 tty 驅(qū)動(dòng)程序,則調(diào)用 tty_driver_lookup_tty 函數(shù)來查找對(duì)應(yīng)的 tty 設(shè)備,并將找到的 tty 設(shè)備結(jié)構(gòu)體指針保存到 tty 變量中。如果找到了該 tty 設(shè)備,則需要重新打開該 tty 設(shè)備。否則,需要初始化該 tty 設(shè)備。在初始化 tty 設(shè)備時(shí),需要調(diào)用 tty_init_dev 函數(shù)來為該 tty 設(shè)備分配一個(gè) tty 結(jié)構(gòu)體,并對(duì)其進(jìn)行初始化。
在打開 tty 設(shè)備之后,函數(shù)會(huì)調(diào)用 tty_add_file 函數(shù)將該 tty 設(shè)備與文件結(jié)構(gòu)體相關(guān)聯(lián)。此外,如果該 tty 設(shè)備是一個(gè)偽終端主設(shè)備,則需要將 noctty 標(biāo)志設(shè)置為 1。
最后,函數(shù)會(huì)調(diào)用 tty 設(shè)備的 open 函數(shù),如果存在的話,來進(jìn)行一些特定的操作。如果 open 函數(shù)返回錯(cuò)誤碼,則需要釋放該 tty 設(shè)備并返回錯(cuò)誤碼。如果 open 函數(shù)返回 -ERESTARTSYS,則需要重新打開該 tty 設(shè)備。如果有中斷發(fā)生,也需要重新打開該 tty 設(shè)備。
tty_write
tty_write()作用是將用戶數(shù)據(jù)寫入 tty 設(shè)備,并通過線路規(guī)則(line discipline)進(jìn)行處理。
線路規(guī)則是 tty 設(shè)備的一種機(jī)制,用于處理和轉(zhuǎn)換從用戶進(jìn)程到內(nèi)核和設(shè)備的數(shù)據(jù)流。在寫入 tty 設(shè)備之前,需要獲取該 tty 設(shè)備的線路規(guī)則,并調(diào)用其 write 方法進(jìn)行處理。
/** *tty_write-writemethodforttydevicefile *@file:ttyfilepointer *@buf:userdatatowrite *@count:bytestowrite *@ppos:unused * *Writedatatoattydeviceviathelinediscipline. * *Locking: *Locksthelinedisciplineasrequired *Writestothettydriverareserializedbytheatomic_write_lock *andarethenprocessedinchunkstothedevice.Thelinediscipline *writemethodwillnotbeinvokedinparallelforeachdevice. */ staticssize_ttty_write(structfile*file,constchar__user*buf, size_tcount,loff_t*ppos) { structtty_struct*tty=file_tty(file); structtty_ldisc*ld; ssize_tret; if(tty_paranoia_check(tty,file_inode(file),"tty_write")) return-EIO; if(!tty||!tty->ops->write|| (test_bit(TTY_IO_ERROR,&tty->flags))) return-EIO; /*Shorttermdebugtocatchbuggydrivers*/ if(tty->ops->write_room==NULL) printk(KERN_ERR"ttydriver%slacksawrite_roommethod. ", tty->driver->name); ld=tty_ldisc_ref_wait(tty); if(!ld->ops->write) ret=-EIO; else ret=do_tty_write(ld->ops->write,tty,file,buf,count); tty_ldisc_deref(ld); returnret; }
tty_write()所作工作如下:
首先從文件指針中獲取 tty_struct 數(shù)據(jù)結(jié)構(gòu)的指針,表示要寫入的 tty 設(shè)備。
檢查傳入的 tty_struct 指針是否有效,以及是否有其他進(jìn)程正在訪問該 tty 設(shè)備。如果出現(xiàn)問題,返回輸入/輸出錯(cuò)誤碼 -EIO。
檢查 tty_struct 指針是否有效、tty 設(shè)備是否支持寫操作,以及是否已經(jīng)出現(xiàn)了輸入/輸出錯(cuò)誤。如果出現(xiàn)問題,返回輸入/輸出錯(cuò)誤碼 -EIO。
檢查 tty 設(shè)備是否實(shí)現(xiàn)了 write_room 方法,如果沒有,則輸出錯(cuò)誤信息。
獲取 tty 設(shè)備的線路規(guī)則(line discipline),并等待獲取成功。
檢查線路規(guī)則的 write 方法是否存在,如果不存在,返回輸入/輸出錯(cuò)誤碼 -EIO。否則,調(diào)用 do_tty_write 函數(shù),將數(shù)據(jù)寫入 tty 設(shè)備。
釋放線路規(guī)則引用計(jì)數(shù)器。
返回寫入操作的結(jié)果,如果寫入成功,則返回寫入的字節(jié)數(shù);否則,返回相應(yīng)的錯(cuò)誤碼。
tty_read
/** *tty_read-readmethodforttydevicefiles *@file:pointertottyfile *@buf:userbuffer *@count:sizeofuserbuffer *@ppos:unused * *Performthereadsystemcallfunctiononthisterminaldevice.Checks *forhungupdevicesbeforecallingthelinedisciplinemethod. * *Locking: *Locksthelinedisciplineinternallywhileneeded.Multiple *readcallsmaybeoutstandinginparallel. */ staticssize_ttty_read(structfile*file,char__user*buf,size_tcount, loff_t*ppos) { inti; structinode*inode=file_inode(file); structtty_struct*tty=file_tty(file); structtty_ldisc*ld; if(tty_paranoia_check(tty,inode,"tty_read")) return-EIO; if(!tty||(test_bit(TTY_IO_ERROR,&tty->flags))) return-EIO; /*Wewanttowaitforthelinedisciplinetosortoutinthis situation*/ ld=tty_ldisc_ref_wait(tty); if(ld->ops->read) i=ld->ops->read(tty,file,buf,count); else i=-EIO; tty_ldisc_deref(ld); if(i>0) tty_update_time(&inode->i_atime); returni; }
tty_read()實(shí)現(xiàn)終端設(shè)備文件讀操作的函數(shù) 。
獲取 tty_struct 結(jié)構(gòu)體、inode 和 line discipline 對(duì)象的指針。
調(diào)用 tty_paranoia_check() 函數(shù)檢查 tty_struct 結(jié)構(gòu)體是否可用。如果檢查失敗,返回 -EIO。
檢查 tty_struct 結(jié)構(gòu)體是否為空或者 TTY_IO_ERROR 標(biāo)志位已經(jīng)設(shè)置。如果是,則返回 -EIO。
獲取 line discipline 對(duì)象的引用,確保它不會(huì)在 tty_read() 函數(shù)執(zhí)行期間被卸載。
檢查 line discipline 的 read() 方法是否可用。如果可用,則調(diào)用該方法進(jìn)行讀取操作,并將返回的字節(jié)數(shù)保存在變量 i 中。如果不可用,返回 -EIO。
釋放 line discipline 的引用。
如果讀取操作成功,調(diào)用 tty_update_time() 函數(shù)更新 inode 的訪問時(shí)間。
返回讀取的字節(jié)數(shù)。
小結(jié)
在這一節(jié)里,只對(duì)tty的構(gòu)造做一個(gè)分析,具體的比如線路規(guī)程的內(nèi)容我們了解知道就好,這里不做深入分析。
-
顯示器
+關(guān)注
關(guān)注
21文章
4980瀏覽量
140045 -
Linux
+關(guān)注
關(guān)注
87文章
11310瀏覽量
209626 -
串口
+關(guān)注
關(guān)注
14文章
1555瀏覽量
76562 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4332瀏覽量
62666
原文標(biāo)題:【驅(qū)動(dòng)】串口驅(qū)動(dòng)分析(二)-tty core
文章出處:【微信號(hào):嵌入式與Linux那些事,微信公眾號(hào):嵌入式與Linux那些事】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論