前言
tty這個名稱源于電傳打字節的簡稱,在linux表示各種終端,終端通常都跟硬件相對應。比如對應于輸入設備鍵盤鼠標,輸出設備顯示器的控制終端和串口終端。也有對應于不存在設備的pty驅動。在如此眾多的終端模型之中,linux是怎么將它們統一建模的呢?這就是我們今天要討論的問題。
tty驅動概貌
tty架構如下所示:
如上圖所示,用戶空間主要是通過系統調用與tty core交互。tty core根據用空間操作的類型再選擇跟line discipline和tty driver交互。
例如,設置硬件的ioctl指令就直接交給tty_driver處理。read和write操作就會交給 line discipline處理。
Line discipline是線路規程的意思。正如它的名字一樣,它表示的是這條終端”線程”的輸入與輸出規范設置。主要用來進行輸入/輸出數據的預處理。
處理之后,就會將數據交給tty driver ,它將字符轉換成終端可以理解的字串。將其傳給終端設備。
值得注意的是,這個架構沒有為tty driver 提供read操作。也就是說tty core 和line discipline都沒有辦法從tty driver里直接讀終端信息。這是因為tty driver對應的hardware并不一定是輸入數據和輸出 數據的共同負載者。
例如控制終端,輸出設備是顯示器,輸入設備是鍵盤。基于這樣的原理。在line discipline中有一個輸入緩存區,并提供了一個名叫receive_buf()的接口函數。對應的終端設備只要調用line discipine的receiver_buf函數,將數據寫入到輸入緩存區就可以了。如果一個設備同時是輸入設備又是輸出設備。那在設備的中斷處理中調用receive_buf()將數據寫入即可.
tty驅動接口分析
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 子系統的 sysctl 相關設置,包括注冊 sysctl 參數、創建 sysctl 目錄等。
初始化 tty 設備的字符設備對象,并將其與 tty 設備操作函數 tty_fops 綁定。同時,創建一個名為 "tty" 的 tty 設備節點,并將其設備號設置為 MKDEV(TTYAUX_MAJOR, 0)。
初始化控制臺設備的字符設備對象,并將其添加到字符設備系統中。同時,創建一個名為 "console" 的控制臺設備節點,并將其設備號設置為 MKDEV(TTYAUX_MAJOR, 1)。該控制臺設備節點還將在 sysfs 中創建一個名為 "console" 的目錄,并在該目錄下創建多個屬性文件,用于控制控制臺的一些屬性。
如果內核支持虛擬終端,則初始化虛擬終端。
這里我們看到了熟悉的cdev_init(),device_create()之類的函數,這正是字符設備的創建流程。因此,我們說串口驅動也是一個字符設備驅動。
而在serial8250_init()中,會調用platform_driver_register()去注冊serial8250_isa_driver,在設備樹節點和serial8250_isa_driver name匹配的時候,就會進入probe流程。因此,也可以說串口驅動是總線設備驅動模型。
tty_alloc_driver
/*UseTTY_DRIVER_*flagsbelow*/ #definetty_alloc_driver(lines,flags) __tty_alloc_driver(lines,THIS_MODULE,flags)
__tty_alloc_driver()用于分配一個 tty 驅動程序的數據結構 struct tty_driver,并對其一些常用字段進行初始化。
/** *__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); /*分配一個structtty_driver結構體,并對其中的一些字段進行初始化,包括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標志位沒有被設置,那么函數會分配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標志位沒有被設置,那么函數會分配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; } /*函數會根據lines的值分配相應數量的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用于注冊 tty 驅動程序的,被 tty 驅動程序調用以將自己注冊到內核中。
/* *Calledbyattydrivertoregisteritself. */ inttty_register_driver(structtty_driver*driver) { interror; inti; dev_tdev; structdevice*d; /*確認是否要內核動態分配主設備號*/ if(!driver->major){ /*函數調用alloc_chrdev_region函數來動態分配主設備號,并將分配的主設備號和次設備號保存在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{ /*已經預先分配了主設備號,函數調用register_chrdev_region函數來注冊設備號*/ dev=MKDEV(driver->major,driver->minor_start); error=register_chrdev_region(dev,driver->num,driver->name); } if(error0) ??goto?err; ?/*判斷是否設置了?TTY_DRIVER_DYNAMIC_ALLOC?標志位*/ ?if?(driver->flags&TTY_DRIVER_DYNAMIC_ALLOC){ /*需要動態分配tty設備號,函數調用tty_cdev_add函數來添加tty設備號,并將每個tty設備的字符設備注冊到內核中*/ 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標志位是否設置*/ if(!(driver->flags&TTY_DRIVER_DYNAMIC_DEV)){ for(i=0;inum;i++){ /*需要注冊固定的tty設備號,函數在循環中調用tty_register_device函數來注冊每個tty設備號,并將每個tty設備注冊到內核中*/ d=tty_register_device(driver,i,NULL); if(IS_ERR(d)){ error=PTR_ERR(d); gotoerr_unreg_devs; } } } /*注冊/proc/tty/drivers目錄中的信息*/ proc_tty_register_driver(driver); /*將driver結構體中的flags字段設置為TTY_DRIVER_INSTALLED,表示該驅動程序已經被成功注冊到內核中*/ 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()函數操作比較簡單。就是為tty_driver創建字符設備。然后將字符設備的操作集指定為tty_fops。并且將tty_driver 掛載到tty_drivers鏈表中。這個鏈表中是以設備號為關鍵字找到對應的driver。
特別的。如果沒有定義TTY_DRIVER_DYNAMIC_DEV。還會在sysfs中創建一個類設備。這樣主要是為了udev管理設備。
tty_unregister_device
tty_unregister_device用于注銷一個 tty 設備。該函數的作用是銷毀設備節點和字符設備,以便于釋放與該 tty 設備相關的資源,例如內存和設備文件等.
/** *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所做工作如下:
調用 device_destroy 函數來銷毀 tty 設備對應的設備節點。接受兩個參數:第一個參數 tty_class 表示 tty 類,第二個參數是 tty 設備的設備號,其中 MKDEV(driver->major, driver->minor_start) + index 表示 tty 設備的設備號,driver->major 表示 tty 設備的主設備號,driver->minor_start 表示 tty 設備的次設備號的起始值,index 表示 tty 設備的索引
如果該 tty 驅動程序不是動態分配的,則調用 cdev_del 函數來注銷該 tty 設備對應的字符設備。
get_tty_driver
get_tty_driver作用是在用戶空間的應用程序使用 tty 設備時,獲取對應的 tty 驅動程序的信息。
/** *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 循環遍歷全局鏈表 tty_drivers,該鏈表中保存了所有已經注冊的 tty 驅動程序。對于每個 tty 驅動程序,函數將其設備號的起始值和結束值計算出來,如果給定設備號不在這個范圍內,則繼續遍歷下一個 tty 驅動程序。
如果給定設備號在某個 tty 驅動程序的范圍內,則計算出該設備號對應的 tty 設備的索引值,并調用 tty_driver_kref_get 函數來獲取該 tty 驅動程序的引用計數。函數返回該 tty 驅動程序的結構體指針,并將找到的 tty 設備的索引值保存到 index 參數中。
需要注意的是,函數在訪問全局鏈表 tty_drivers 時,需要持有互斥鎖 tty_mutex。因為多個應用程序可能同時訪問同一個 tty 驅動程序,如果沒有互斥鎖保護,可能會導致并發問題。
tty_open
從注冊的過程可以看到,所有的操作都會對應到tty_fops中。Open操作對應的操作接口是tty_open(),用于打開一個 tty 設備。函數的作用是在用戶空間的應用程序使用 tty 設備時,打開對應的 tty 設備,并初始化相應的數據結構。
/** *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: /*分配一個tty結構體*/ retval=tty_alloc_file(filp); if(retval) return-ENOMEM; /*檢查文件的標志位,如果包含O_NOCTTY標志,則禁止將該tty設備設置為控制終端*/ noctty=filp->f_flags&O_NOCTTY; index=-1; retval=0; /*嘗試打開當前的tty設備*/ tty=tty_open_current_tty(device,filp); if(!tty){ mutex_lock(&tty_mutex); /*根據設備號來查找對應的tty驅動程序,并初始化該tty設備,將找到的tty驅動程序保存到driver變量中*/ driver=tty_lookup_driver(device,filp,&noctty,&index); if(IS_ERR(driver)){ retval=PTR_ERR(driver); gotoerr_unlock; } /*checkwhetherwe'rereopeninganexistingtty*/ /*查找對應的tty設備,并將找到的tty設備結構體指針保存到tty變量中*/ tty=tty_driver_lookup_tty(driver,inode,index); if(IS_ERR(tty)){ retval=PTR_ERR(tty); gotoerr_unlock; } if(tty){ /*如果找到了該tty設備,則需要重新打開該tty設備*/ 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?設備*/ ???tty?=?tty_init_dev(driver,?index); ????????????/*為該?tty?設備分配一個?tty?結構體,并對其進行初始化*/ ???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?設備與文件結構體相關聯*/ ?tty_add_file(tty,?filp); ?check_tty_count(tty,?__func__); ????/*如果該?tty?設備是一個偽終端主設備,則需要將?noctty?標志設置為?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); /*調用tty設備的open函數*/ 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; }
函數所作工作如下:
在打開 tty 設備時,該函數會檢查文件的標志位,如果包含 O_NOCTTY 標志,則禁止將該 tty 設備設置為控制終端。這是因為如果一個進程打開一個 tty 設備并將其設置為控制終端,其他進程就無法再將該 tty 設備設置為控制終端,這可能會導致一些問題。
如果打開當前的 tty 設備失敗,則需要根據設備號來查找對應的 tty 驅動程序,并初始化該 tty 設備。在查找 tty 驅動程序時,需要調用 tty_lookup_driver 函數來查找對應的 tty 驅動程序,并將找到的 tty 驅動程序保存到 driver 變量中。如果找不到對應的 tty 驅動程序,則返回錯誤碼。
如果找到了對應的 tty 驅動程序,則調用 tty_driver_lookup_tty 函數來查找對應的 tty 設備,并將找到的 tty 設備結構體指針保存到 tty 變量中。如果找到了該 tty 設備,則需要重新打開該 tty 設備。否則,需要初始化該 tty 設備。在初始化 tty 設備時,需要調用 tty_init_dev 函數來為該 tty 設備分配一個 tty 結構體,并對其進行初始化。
在打開 tty 設備之后,函數會調用 tty_add_file 函數將該 tty 設備與文件結構體相關聯。此外,如果該 tty 設備是一個偽終端主設備,則需要將 noctty 標志設置為 1。
最后,函數會調用 tty 設備的 open 函數,如果存在的話,來進行一些特定的操作。如果 open 函數返回錯誤碼,則需要釋放該 tty 設備并返回錯誤碼。如果 open 函數返回 -ERESTARTSYS,則需要重新打開該 tty 設備。如果有中斷發生,也需要重新打開該 tty 設備。
tty_write
tty_write()作用是將用戶數據寫入 tty 設備,并通過線路規則(line discipline)進行處理。
線路規則是 tty 設備的一種機制,用于處理和轉換從用戶進程到內核和設備的數據流。在寫入 tty 設備之前,需要獲取該 tty 設備的線路規則,并調用其 write 方法進行處理。
/** *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 數據結構的指針,表示要寫入的 tty 設備。
檢查傳入的 tty_struct 指針是否有效,以及是否有其他進程正在訪問該 tty 設備。如果出現問題,返回輸入/輸出錯誤碼 -EIO。
檢查 tty_struct 指針是否有效、tty 設備是否支持寫操作,以及是否已經出現了輸入/輸出錯誤。如果出現問題,返回輸入/輸出錯誤碼 -EIO。
檢查 tty 設備是否實現了 write_room 方法,如果沒有,則輸出錯誤信息。
獲取 tty 設備的線路規則(line discipline),并等待獲取成功。
檢查線路規則的 write 方法是否存在,如果不存在,返回輸入/輸出錯誤碼 -EIO。否則,調用 do_tty_write 函數,將數據寫入 tty 設備。
釋放線路規則引用計數器。
返回寫入操作的結果,如果寫入成功,則返回寫入的字節數;否則,返回相應的錯誤碼。
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()實現終端設備文件讀操作的函數 。
獲取 tty_struct 結構體、inode 和 line discipline 對象的指針。
調用 tty_paranoia_check() 函數檢查 tty_struct 結構體是否可用。如果檢查失敗,返回 -EIO。
檢查 tty_struct 結構體是否為空或者 TTY_IO_ERROR 標志位已經設置。如果是,則返回 -EIO。
獲取 line discipline 對象的引用,確保它不會在 tty_read() 函數執行期間被卸載。
檢查 line discipline 的 read() 方法是否可用。如果可用,則調用該方法進行讀取操作,并將返回的字節數保存在變量 i 中。如果不可用,返回 -EIO。
釋放 line discipline 的引用。
如果讀取操作成功,調用 tty_update_time() 函數更新 inode 的訪問時間。
返回讀取的字節數。
小結
在這一節里,只對tty的構造做一個分析,具體的比如線路規程的內容我們了解知道就好,這里不做深入分析。
-
顯示器
+關注
關注
21文章
4970瀏覽量
139926 -
Linux
+關注
關注
87文章
11292瀏覽量
209331 -
串口
+關注
關注
14文章
1551瀏覽量
76428 -
函數
+關注
關注
3文章
4327瀏覽量
62573
原文標題:【驅動】串口驅動分析(二)-tty core
文章出處:【微信號:嵌入式與Linux那些事,微信公眾號:嵌入式與Linux那些事】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論