1、tcdrain() 函数标准 C 语言 tcdrain(fd) 等待直到所有写入 fd 引用的对象的输出都被传输fcntl() 函数#include 定 义 函 数 int fcntl(int fd , int cmd); int fcntl(int fd,int cmd,long arg); int fcntl(int fd,int cmd,struct flock * lock); fcntl()用 来 操 作 文 件 描 述 符 的 一 些 特 性 。参 数 fd 代 表 欲 设 置 的 文 件 描 述 词 。参 数 cmd 代 表 欲 操 作 的 指 令 。 有 以 下 几 种 情 况
2、 : F_DUPFD 用 来 查 找 大 于 或 等 于 参 数 arg 的 最 小 且 仍 未 使 用 的 文 件 描 述 词 , 并 且 复 制 参 数 fd 的文 件 描 述 词 。 执 行 成 功 则 返 回 新 复 制 的 文 件 描 述 词 。 请 参 考 dup2()。F_GETFD 取 得 close-on-exec 旗 标 。若 此 旗 标 的 FD_CLOEXEC 位 为 0, 代 表 在 调 用 exec()相 关 函 数 时 文 件 将 不 会 关 闭 。 F_SETFD 设 置 close-on-exec 旗 标 。 该 旗 标 以 参 数 arg 的 FD_CLOE
3、XEC 位 决 定 。 F_GETFL 取 得 文 件 描 述 词 状 态 旗 标 , 此 旗 标 为 open( ) 的 参 数 flags。 F_SETFL 设 置 文 件 描 述 词 状 态 旗 标 , 参 数 arg 为 新 旗 标 , 但 只 允 许 O_APPEND、 O_NONBLOCK和 O_ASYNC 位 的 改 变 , 其 他 位 的 改 变 将 不 受 影 响 。 F_GETLK 取 得 文 件 锁 定 的 状 态 。 F_SETLK 设 置 文 件 锁 定 的 状 态 。此 时 flcok 结 构 的 l_type 值 必 须 是 F_RDLCK、 F_WRLCK 或
4、F_UNLCK。 如 果 无 法 建 立锁 定 , 则 返 回 -1, 错 误 代 码 为 EACCES 或 EAGAIN。 F_SETLKW 与 F_SETLK 作 用 相 同 , 但 是 无 法 建 立 锁 定 时 , 此 调 用 会 一 直 等 到 锁 定 动 作 成 功 为 止 。若 在 等 待 锁 定 的 过 程 中 被 信 号 中 断 时 , 会 立 即 返 回 -1, 错 误 代 码 为 EINTR。参 数 lock 指 针 为 flock 结 构 指 针 , 定 义 如 下 struct flcok short int l_type; short int l_whence; o
5、ff_t l_start; off_t l_len; pid_t l_pid; ; l_type 有 三 种 状 态 : F_RDLCK 建 立 一 个 供 读 取 用 的 锁 定 F_WRLCK 建 立 一 个 供 写 入 用 的 锁 定 F_UNLCK 删 除 之 前 建 立 的 锁 定 l_whence 也 有 三 种 方 式 : SEEK_SET 以 文 件 开 头 为 锁 定 的 起 始 位 置 。 SEEK_CUR 以 目 前 文 件 读 写 位 置 为 锁 定 的 起 始 位 置 SEEK_END 以 文 件 结 尾 为 锁 定 的 起 始 位 置 。 返 回 值 成 功 则 返
6、 回 0, 若 有 错 误 则 返 回 -1, 错 误 原 因 存 于 errno.ioctl()函数一、 ioctl 是设备驱动程序中对设备的 I/O 通道进行管理的函数。所谓对 I/O 通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用形式如下: int ioctl(int fd, ind cmd, ); 其中 fd 就是用户程序打开设备时使用 open 函数返回的文件标示符, cmd 就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和 cmd 的意义相关的。 ioctl 函数是文件结构中的一个属性分量,就是
7、说如果你的驱动程序提供了对 ioctl 的支持,用户就可以在用户程序中使用 ioctl 函数控制设备的 I/O 通道。 二、 ioctl 的必要性 如果不用 ioctl 的话,也可以实现对设备 I/O 通道的控制,但那就是蛮拧了。例如,我们可以在 驱动程序中实现 write 的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令(一般在 socket 编程中常常这样做) 。但是如果这样做的话,会导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。 所以,我们就使用 ioctl 来实现控制的功能。要记住,用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释
8、这些命令和怎么实现这些命令,这都是驱动程序要做的事情。 三、 ioctl 如何实现 这是一个很麻烦的问题,我是能省则省。要说清楚它,没有四五千字是不行的,所以我这里是不可能把它说得非常清楚了,不过如果有读者对用户程序怎么和驱动程序联系起来感兴趣的话,可以看我前一阵子写的write 的奥秘 。读者只要把 write 换成 ioctl,就知道用户程序的 ioctl 是怎么和驱动程序中的 ioctl实现联系在一起的了。 我这里说一个大概思路,因为我觉得Linux 设备驱动程序这本书已经说的非常清楚了,但是得花一些时间来看。 在驱动程序中实现的 ioctl 函数体内,实际上是有一个 switchcas
9、e结构,每一个 case 对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在 ioctl 中命令码是唯一联系用户程序命令和驱动程序支持的途径。命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。 所以在 Linux 核心中是这样定义一个命令码的: _| 设备类
10、型 | 序列号 | 方向 |数据尺寸|-|-|-|-| 8 bit | 8 bit |2 bit |814 bit|-|-|-|-|这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以 Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。这些宏我就不在这里解释了,具体的形式请读者察看 Linux 核心源代码中的文件里给出了这些宏完整的定义。这里我只多说一个地方,那就是“幻数“ 。幻数是一个字母,数据长度也是 8,所以就用一个特定的字
11、母来标明设备类型,这和用一个数字是一样的,只是更加利于记忆和理解。就是这样,再没有更复杂的了。 更多的说了也没有,读者还是看一看源代码吧,推荐各位阅读Linux 设备驱动程序所带源代码中的 short 一例,因为它比较短小,功能比较简单,可以看明白 ioctl 的功能和细节。 四、cmd 参数如何得出 这里确实要说一说,cmd 参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switchcase结构进行相应的操作。 要透彻理解,只能是通
12、过阅读源代码,我这篇文章实际上只是一个引子。Cmd 参数的组织还是比较复杂的,我认为要搞熟它还是得花不少时间的,但是这是值得的,驱动程序中最难的是对中断的理解。 五、小结 ioctl 其实没有什么很难的东西需要理解,关键是理解 cmd 命令码是怎么在用户程序里生成并在驱动程序里解析的,程序员最主要的工作量在 switchcase结构中,因为对设备的 I/O 控制都是通过这一部分的代码实现的。 flock()函数当用户想使用函数 flock()来锁定一个文件时,可以使用两种不同的锁定类型:一个是共享锁定,另一个是互斥锁定。如果用户采用共享锁定,若干个进程可以对某个文件拥有一个共同的锁定;如果用户
13、申请了一个互斥锁定,其他进程都无法对该文件进行锁定。因此,根据它们的原理,用户可以对执行读取操作的系统采用一个共享锁定,而对执行写入操作的系统采用一个互斥锁定。 (摘自Linux 编程宝典 ) 那么我就有疑问了: 1.既然读取操作的时候是不会对文件内容进行改变的,各个进程是不会相互冲突的,那么为什么要锁定?锁定的意义何在? 2.读取操作的时候使用共享锁定,也就是说,其它进程也可以锁定,那么是不是进行写操作的进程也可以锁定也可以写入呢?共享锁定没有什么作用? 1. 若干进程在读,他们不会互相影响,但是,如果有进程写则会影响它们,所以这个共享锁是防止有进程对文件写 2. 第二个问题和第一个问题关联
14、,共享锁和独占锁互斥。只有当一个程序试图施加它自己的锁时,锁才会起作用;而没有尝试对文件上锁的程序仍然能够访问该文件。因此,锁只能在协同工作的程序间起作用。 ”(摘自GNU/Linux 编程指南 )3. 据程序试验结果:当我对一个文件进行互斥锁定以后,然后进行共享锁定时,锁定没有成功这是因为互斥锁定是排斥其他锁定的;当我对一个文件进行共享锁定以后,然后进行互斥锁定,锁定成功!难道互斥锁定不排斥之前的共享锁定?请解答。4. 我错在先关闭了文件描述符,当然共享锁定也就关闭了,随之互斥锁定必然也就能够成功,我错了,不好意思。但是,据我试验:如果一个进程对文件进行了互斥锁定,另外一个进程没有对文件进行
15、尝试锁定操作而是直接对文件进行写操作,结果是可以写入!? 5. 那么也就是说,如果进程没有考虑到锁定的问题时,还是会破坏我锁定想保持不变的文件?那么这个锁定的意义是不是大打折扣了? 6. 呵呵!这样的锁叫建议锁和建议一样 可听可不听 好像还有强制锁 不过我没用过 呵呵 APUE 上有介绍。如果你和别的程序混用无法控制他们的话,建议你采用 QMAIL 一样的原子操作法,不需要锁。UART 的 CTS 与 RTS 在 RS232 中本来 CTS 与 RTS 有明确的意义,但自从贺氏( HAYES ) 推出了聪明猫(SmartModem)后就有点混淆了。在 RS232 中 RTS 与 CTS 是用来
16、半双工模式下的方向切换;HAYES Modem 中的 RTS ,CTS 是用来进 行硬件流控的。通常 UART 的 RTC、CTS 的含义指后者,即用来做硬流控的。硬流控的 RTS 、CTS :RTS (Require To Send,发送请求)为输出信号,用于指示本设备准备好可接收;CTS (Clear To Send,发送清除)为输入信号,有效时停止发送。假定 A、B 两设备通信,A 设备的 RTS 连接 B 设备的 CTS ;A 设备的 CTS 连接 B 设备 的 RTS 。 前一路信号控制 B 设备的发送,后一路信号控制 A 设备的发送。对 B 设备的发送(A 设备接收)来说,如果 A
17、 设备接收缓冲快满的时发出 RTS 信号(意思 通知 B 设备停止发送),B 设备通过 CTS 检测到该信号,停止发送;一段时间后 A 设备接收缓冲有了空余,发出 RTS 信号,指示 B 设备开始发送数据。A 设备发(B 设备接收) 类似。上述功能也能在数据流中插入 Xoff(特殊字符)和 Xon(另一个特殊字符)信号来实现。A 设备一旦接收到 B 设备发送过来的 Xoff,立刻停止发 送;反之,如接收到 B 设备发送过来的 Xon,则恢复发送数据给 B 设备。同理,B 设备也类似,从而实现收发双方的速度匹配。半双工的方向切换:RS232 中使用 DTR(Date Terminal Ready
18、,数据终端准备)与DSR(Data Set Ready ,数据设备准备好)进行主流控,类似上述的 RTS 与 CTS 。对半双工的通信的 DTE( Date Terminal Equipment,数据终端设备)与 DCE(Data circuit Equipment )来说,默认的方向是 DTE 接收,DCE 发送。如果 DTE 要发送数据,必须发出 RTS 信号,请求发送数据。DCE 收到后如果 空闲则发出 CTS 回 应 RTS 信 号,表示响应请求,这样通信方向就变为 DTE-TCE,同时 RTS 与 CTS 信号必须一直保持。从这里可以看出, CTS ,TRS 虽然也有点流控的意思(如
19、 CTS 没有发出,DTE 也不能发送数据),但主要是用来进行方向切换的。如果 UART 只有 RX、TX 两个信号,要流控的话只能是软流控;如果有 RX,TX,CTS ,RTS 四个信号,则多半是支持硬流控的 UART;如果有 RX,TX,CTS ,RTS ,DTR,DSR 六个信号的话,RS232 标准的可能性比较大。顺便提一下:DCD( Data Carrier Detect, 数据载波检测):DCE 向 DTE 指示,线路上检测到载波。RI(Ring Indicator,振铃指示):DCE 向 DTE 指示,有呼叫接入。linux 基础复习(7)串口应用开发据通信的基本方式可分为并行通
20、信与串行通信两种。 并行通信是指利用多条数据传输线将一个资料的各位同时传送。它的特点是传输速度快,适用于短距离通信,但要求传输速度较高的应用场合。 串行通信是指利用一条传输线将资料一位位地顺序传送。特点是通信线路简单,利用简单的线缆就可实现通信,降低成本,适用于远距离通信,但传输速度慢的应用场合。串口设置详解本节主要讲解设置串口的主要方法。如前所述,设置串口中最基本的包括波特率设置,校验位和停止位设置。串口的设置主要是设置 struct termios 结构体的各成员值,如下所示:includestruct termio unsigned short c_iflag; /* 输入模式标志 */
21、unsigned short c_oflag; /* 输出模式标志 */unsigned short c_cflag; /* 控制模式标志*/unsigned short c_lflag; /*本地模式标志 */unsigned char c_line; /* line discipline */unsigned char c_ccNCC; /* control characters */;在这个结构中最为重要的是 c_cflag,通过对它的赋值,用户可以设置波特率、字符大小、数据位、停止位、奇偶校验位和硬件流控等。另外 c_iflag 和 c_cc 也是比较常用的标志。在此主要对这 3 个成
22、员进行详细说明。c_cflag 支持的常量名称CBAUD 波特率的位掩码B0 0 波特率(放弃 DTR)B1800 1800 波特率B2400 2400 波特率B4800 4800 波特率B9600 9600 波特率B19200 19200 波特率B38400 38400 波特率B57600 57600 波特率B115200 115200 波特率EXTA 外部时钟率EXTB 外部时钟率CSIZE 数据位的位掩码CS5 5 个数据位CS6 6 个数据位CS7 7 个数据位CS8 8 个数据位CSTOPB 2 个停止位(不设则是 1 个停止位)CREAD 接收使能PARENB 校验位使能PAROD
23、D 使用奇校验而不使用偶校验HUPCL 最后关闭时挂线(放弃 DTR)CLOCAL 本地连接(不改变端口所有者)LOBLK 块作业控制输出CNET_CTSRTS 硬件流控制使能c_iflag 支持的常量名称INPCK 奇偶校验使能IGNPAR 忽略奇偶校验错误PARMRK 奇偶校验错误掩码ISTRIP 除去奇偶校验位IXON 启动出口硬件流控IXOFF 启动入口软件流控IXANY 允许字符重新启动流控IGNBRK 忽略中断情况BRKINT 当发生中断时发送 SIGINT 信号INLCR 将 NL 映射到 CRIGNCR 忽略 CRICRNL 将 CR 映射到 NLIUCLC 将高位情况映射到低
24、位情况IMAXBEL 当输入太长时回复 ECHOc_cc 支持的常量名称VINTR 中断控制,对应键为 CTRL+CVQUIT 退出操作,对应键为 CRTL+ZVERASE 删除操作,对应键为 Backspace(BS)VKILL 删除行,对应键为 CTRL+UVEOF 位于文件结尾,对应键为 CTRL+DVEOL 位于行尾,对应键为 Carriage return(CR )VEOL2 位于第二行尾,对应键为 Line feed(LF )VMIN 指定了最少读取的字符数VTIME 指定了读取每个字符的等待时间串口控制函数Tcgetattr 取属性(termios 结构)Tcsetattr 设置
25、属性(termios 结构)cfgetispeed 得到输入速度Cfgetospeed 得到输出速度Cfsetispeed 设置输入速度Cfsetospeed 设置输出速度Tcdrain 等待所有输出都被传输tcflow 挂起传输或接收tcflush 刷清未决输入和/或输出Tcsendbreak 送 BREAK 字符tcgetpgrp 得到前台进程组 IDtcsetpgrp 设置前台进程组 ID完整的串口配置模板,实用!把常用的选项在函数里面列出,可大大方便用户的调试使用int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nSt
26、op)struct termios newtio,oldtio;/*保存测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息*/if ( tcgetattr( fd,return -1;bzero( /*步骤一,设置字符大小*/newtio.c_cflag |= CLOCAL | CREAD;newtio.c_cflag /*设置停止位*/switch( nBits )case 7:newtio.c_cflag |= CS7;break;case 8:newtio.c_cflag |= CS8;break;/*设置奇偶校验位*/switch( nEvent )case O: /奇数
27、newtio.c_cflag |= PARENB;newtio.c_cflag |= PARODD;newtio.c_iflag |= (INPCK | ISTRIP);break;case E: /偶数newtio.c_iflag |= (INPCK | ISTRIP);newtio.c_cflag |= PARENB;newtio.c_cflag break;case N: /无奇偶校验位newtio.c_cflag break;/*设置波特率*/switch( nSpeed )case 2400:cfsetispeed(cfsetospeed(break;case 4800:cfseti
28、speed(cfsetospeed(break;case 9600:cfsetispeed(cfsetospeed(break;case 115200:cfsetispeed(cfsetospeed(break;case 460800:cfsetispeed(cfsetospeed(break;default:cfsetispeed(cfsetospeed(break;/*设置停止位*/if( nStop = 1 )newtio.c_cflag else if ( nStop = 2 )newtio.c_cflag |= CSTOPB;/*设置等待时间和最小接收字符*/ newtio.c_cc
29、VTIME = 0;newtio.c_ccVMIN = 0;/*处理未接收字符*/tcflush(fd,TCIFLUSH);/*激活新配置*/if(tcsetattr(fd,TCSANOW,return -1;printf(“set done!n“);return 0; 串口使用详解在配置完串口的相关属性后,就可对串口进行打开,读写操作了。其使用方式与文件操作一样,区别在于串口是一个终端设备。打开串口fd = open( “/dev/ttyS0“, O_RDWR|O_NOCTTY|O_NDELAY);Open 函数中除普通参数外,另有两个参数 O_NOCTTY 和 O_NDELAY。O_NOC
30、TTY: 通知 linix 系统,这个程序不会成为这个端口的控制终端。O_NDELAY: 通知 linux 系统不关心 DCD 信号线所处的状态(端口的另一端是否激活或者停止) 。然后,恢复串口的状态为阻塞状态,用于等待串口数据的读入。用 fcntl 函数:fcntl(fd, F_SETFL, 0);接着,测试打开的文件描述府是否引用一个终端设备,以进一步确认串口是否正确打开。isatty(STDIN_FILENO);串口的读写与普通文件一样,使用 read,write 函数。read(fd,buff,8);write(fd,buff,8);实例#include stdio.h#include
31、 string.h#include sys/types.h#include errno.h#include sys/stat.h#include fcntl.h#include unistd.h#include termios.h#include stdlib.hint set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)struct termios newtio,oldtio;if ( tcgetattr( fd,return -1;bzero( newtio.c_cflag |= CLOCAL | CREAD; newt
32、io.c_cflag switch( nBits )case 7:newtio.c_cflag |= CS7;break;case 8:newtio.c_cflag |= CS8;break;switch( nEvent )case O:newtio.c_cflag |= PARENB;newtio.c_cflag |= PARODD;newtio.c_iflag |= (INPCK | ISTRIP);break;case E: newtio.c_iflag |= (INPCK | ISTRIP);newtio.c_cflag |= PARENB;newtio.c_cflag break;c
33、ase N: newtio.c_cflag break;switch( nSpeed )case 2400:cfsetispeed(cfsetospeed(break;case 4800:cfsetispeed(cfsetospeed(break;case 9600:cfsetispeed(cfsetospeed(break;case 115200:cfsetispeed(cfsetospeed(break;default:cfsetispeed(cfsetospeed(break;if( nStop = 1 )newtio.c_cflag else if ( nStop = 2 )newti
34、o.c_cflag |= CSTOPB;newtio.c_ccVTIME = 0;newtio.c_ccVMIN = 0;tcflush(fd,TCIFLUSH);if(tcsetattr(fd,TCSANOW,return -1;printf(“set done!n“);return 0;int open_port(int fd,int comport)char *dev=“/dev/ttyS0“,“/dev/ttyS1“,“/dev/ttyS2“;long vdisable;if (comport=1) fd = open( “/dev/ttyS0“, O_RDWR|O_NOCTTY|O_
35、NDELAY);if (-1 = fd)perror(“Cant Open Serial Port“);return(-1);else printf(“open ttyS0 .n“);else if(comport=2) fd = open( “/dev/ttyS1“, O_RDWR|O_NOCTTY|O_NDELAY);if (-1 = fd)perror(“Cant Open Serial Port“);return(-1);else printf(“open ttyS1 .n“);else if (comport=3)fd = open( “/dev/ttyS2“, O_RDWR|O_N
36、OCTTY|O_NDELAY);if (-1 = fd)perror(“Cant Open Serial Port“);return(-1);else printf(“open ttyS2 .n“);if(fcntl(fd, F_SETFL, 0)0)printf(“fcntl failed!n“);elseprintf(“fcntl=%dn“,fcntl(fd, F_SETFL,0);if(isatty(STDIN_FILENO)=0)printf(“standard input is not a terminal devicen“);elseprintf(“isatty success!n
37、“);printf(“fd-open=%dn“,fd);return fd;int main(void)int fd;int nread,i;char buff=“Hellon“;if(fd=open_port(fd,1)0)perror(“open_port error“);return;if(i=set_opt(fd,115200,8,N,1)0)perror(“set_opt error“);return;printf(“fd=%dn“,fd);/ fd=3;nread=read(fd,buff,8);printf(“nread=%d,%sn“,nread,buff);close(fd);return;