第5章 连接控制:学习stty

1.设备就像文件

在unix系统中每个设备都被当做是文件

查看unix系统的设备 ls -C /dev | head -5

设备不仅具有文件名,而且支持所有与文件相关的系统调用

第5章 连接控制:学习stty

设备文件具有磁盘文件的大部分属性,例如上面ls的输出内容表明/dev/pts/2拥有i-节点4,文件类型是'c',权限位,1个链接,文件所有者bruce和组tty,最近修改时间。

设备是链接不是容器,设备i-节点存储的是指向内核子程序的指针,而不是文件的属性。内核中传输设备数据的子程序被称为设备驱动。在/dev/pts/2中,从终端进行数据传输的代码是在设备进程表中编号为136的子程序,该子程序接收一个参数,在/dev/pts/2中参数为2。

目录是文件名和i-节点的列表,目录并不能区分哪些文件名代表磁盘文件,哪些文件名代表设备。文件类型的区别体现在i-节点上,i-节点的类型被记录在结构stat的成员变量st_mode的类型区域中。磁盘文件的i-节点包含指向数据块的指针,设备文件的i-节点包含指向内核子程序表的指针。

2.磁盘连接的属性

(1)属性1:缓冲

关闭磁盘缓冲

#include<fcntl.h>
int s;
s = fcntl(fd, F_GETFL); //get flags
s | = O_SYNC;  //get SYNC bit
result = fcntl(fd, F_SETFL, s); //set flags
if(result == 1)
    perror("setting SYNC");

fcntl函数

第5章 连接控制:学习stty

(2)文件描述符的另一个属性是自动添加模式,自动添加模式对于若干个进程在同一时间写入文件是很有用的。

第5章 连接控制:学习stty

两个进程会引起竞争,造成文件写入数据混乱。

内核提供了一个简单的解决方法,自动添加模式。当文件描述符的O_APPEND位被开启后,每个对write的调用自动调用lseek将内容添加到文件的末尾。内核将lseek和write组合成一个原子操作,被连接成一个不可分割的单元。

#include<fcntl.h>
int s;
s = fcntl(fd, F_GETFL);
s |= O_APPEND;
result = fcntl(fd, F_SETFL, s);
if(result == -1)
    perror("setting APPEND");
else
    write(fd, &rec, 1);

也可以使用open来设置文件描述符的属性位

fd = open(file_name, O_WRONLY  |  O_APPEND | O_SYNC);

fd = open(file_name, O_CREAT  |  O_TRUNC | O_WRONLY, permission_bits);

O_CREAT   如果不存在,创建该文件

O_TRUNC   如果文件存在,将文件长度置为0

O_EXCL  O_EXCL标志位防止两个进程创建同样的文件

内核在磁盘和进程间传输数据,内核中进行这些传输的代码有很多选项,程序可使用open和fcntl系统调用控制这些数据传输的内部运作。

3.终端连接的属性

/*  listchars.c
 *      purpose: list individually all the chars seen on input
 *       output: char and ascii code, one pair per line
 *        input: stdin, until the letter Q
 *        notes: usesful to show that buffering/editing exists
 */

#include<stdio.h>

void main()
{
        int     c, n = 0;

        while( ( c = getchar()) != 'Q' )
                printf("char %3d is %c code %d\n", n++, c, c );
}

函数输出结果

第5章 连接控制:学习stty

上述输出说明:

1.进程在用户输入return后才接收数据

2.进程将用户输入的return看作是换行符

3.进程发送换行符,终端接收回车换行符

(1)终端驱动程序

第5章 连接控制:学习stty

stty命令让用户读取和修改终端驱动程序的设置

第5章 连接控制:学习stty

(2)编写终端驱动程序

改变终端驱动程序的设置:

第一步从驱动获得属性,第二步修改所要修改的属性,第三步将修改的属性送回驱动程序。

以下代码为一个连接开启字符回显

#include<termios.h>
struct termios settings;
tcgetattr(fd, &settings);
settings.c_lflag | = ECHO;
tcsetattr(fd, TCSANOW, &settings);

库函数tcgetattr和tcsetattr函数提供对终端驱动程序的访问

第5章 连接控制:学习stty

第5章 连接控制:学习stty

when表示在什么时候更新驱动程序设置,TCSANOW表示立即更新驱动程序设置,TCSADRAIN等待直到驱动程序队列中的所有输出都被传送到终端,TCSAFLUSH等待直到驱动程序队列中的所有输出都被传送出去。

termios结构体类型包含若干个标志集和一个控制字符的数组

struct termios
{
    tcflag_t c_iflag;      /* input modes */
    tcflag_t c_oflag;      /* output modes */
    tcflag_t c_cflag;      /* control modes */
    tcflag_t c_lflag;      /* local modes */
    cc_t     c_cc[NCCS];   /* special characters */
}

第5章 连接控制:学习stty

标志集位的使用

第5章 连接控制:学习stty

4.终端驱动程序例子

(1)显示回显位的状态

/* echostate.c
 *   reports current state of echo bit in tty driver for fd 0
 *   shows how to read attributes from driver and test a bit
 */

#include<stdio.h>
#include<termios.h>
#include<stdlib.h>

void main()
{
        struct termios info;
        int rv;

        rv = tcgetattr( 0, &info );     /* read values from driver      */

        if ( rv == -1 ){
                perror( "tcgetattr");
                exit(1);
        }
        if ( info.c_lflag & ECHO )
                printf(" echo is on , since its bit is 1\n");
        else
                printf(" echo if OFF, since its bit is 0\n");
}

第5章 连接控制:学习stty

(2)改变回显位的状态

/* setecho.c
 *   usage:  setecho [y|n]
 *   shows:  how to read, change, reset tty attributes
 */

#include<stdio.h>
#include<termios.h>
#include<stdlib.h>

#define  oops(s,x) { perror(s); exit(x); }

void main(int ac, char *av[])
{
        struct termios info;

        if ( ac == 1 ) 
		exit(0);

        if ( tcgetattr(0,&info) == -1 )          /* get attribs   */
		oops("tcgettattr", 1);

        if ( av[1][0] == 'y' )
                info.c_lflag |= ECHO ;          /* turn on bit    */
        else
                info.c_lflag &= ~ECHO ;         /* turn off bit   */

        if ( tcsetattr(0,TCSANOW,&info) == -1 ) /* set attribs    */
               oops("tcsetattr",2);
}

第5章 连接控制:学习stty

(3)显示大量驱动程序的属性

/* showtty.c
 *	displays some current tty settings
 */

#include<stdio.h>
#include<termios.h>
#include<stdlib.h>

struct flaginfo { int	fl_value; char	*fl_name; };

void showbaud(int);
void show_flagset( int thevalue, struct flaginfo thebitnames[] );
void show_some_flags( struct termios *ttyp );
void main()
{
	struct	termios ttyinfo;	/* this struct holds tty info */
						
	if ( tcgetattr( 0 , &ttyinfo ) == -1 ){   /* get info */
		perror( "cannot get params about stdin");
		exit(1);
	}
						/* show info */
	showbaud ( cfgetospeed( &ttyinfo ) );	/* get + show baud rate	*/
	printf("The erase character is ascii %d, Ctrl-%c\n",
			ttyinfo.c_cc[VERASE], ttyinfo.c_cc[VERASE]-1+'A');
	printf("The line kill character is ascii %d, Ctrl-%c\n",
			ttyinfo.c_cc[VKILL], ttyinfo.c_cc[VKILL]-1+'A');

	show_some_flags( &ttyinfo );		/* show misc. flags	*/
}

void showbaud( int thespeed )
/*
 *	prints the speed in english
 */
{
	printf("the baud rate is ");
	switch ( thespeed ){
		case B300:	printf("300\n");	break;
		case B600:	printf("600\n"); 	break;
		case B1200:	printf("1200\n"); 	break;
		case B1800:	printf("1800\n"); 	break;
		case B2400:	printf("2400\n"); 	break;
		case B4800:	printf("4800\n"); 	break;
		case B9600:	printf("9600\n"); 	break;
		default:	printf("Fast\n");	break;
	}
}

struct flaginfo input_flags[] = {

		IGNBRK	,	"Ignore break condition",
		BRKINT	,	"Signal interrupt on break",
		IGNPAR	,	"Ignore chars with parity errors",
		PARMRK	,	"Mark parity errors",
		INPCK	,	"Enable input parity check",
		ISTRIP	,	"Strip character",
		INLCR	,	"Map NL to CR on input",
		IGNCR	,	"Ignore CR",
		ICRNL	,	"Map CR to NL on input",
		IXON	,	"Enable start/stop output control",
		/* _IXANY  ,	"enable any char to restart output",	*/
		IXOFF   ,	"Enable start/stop input control",
		0	,	NULL };

struct flaginfo local_flags[] = {
		ISIG	,	"Enable signals",
		ICANON	,	"Canonical input (erase and kill)",
		/* _XCASE	,	"Canonical upper/lower appearance", */
		ECHO	,	"Enable echo",
		ECHOE	,	"Echo ERASE as BS-SPACE-BS",
		ECHOK	,	"Echo KILL by starting new line",
		0	,	NULL };

void show_some_flags( struct termios *ttyp )
/*
 *	show the values of two of the flag sets_: c_iflag and c_lflag
 *	adding c_oflag and c_cflag is pretty routine - just add new
 *	tables above and a bit more code below.
 */
{
	show_flagset( ttyp->c_iflag, input_flags );
	show_flagset( ttyp->c_lflag, local_flags );
}

void show_flagset( int thevalue, struct flaginfo thebitnames[] )
/*
 * check each bit pattern and display descriptive title
 */
{
	int	i;
	
	for ( i=0; thebitnames[i].fl_value ; i++ ) {
		printf( "  %s is ", thebitnames[i].fl_name);
		if ( thevalue & thebitnames[i].fl_value )
			printf("ON\n");
		else
			printf("OFF\n");
	}
}

其他设备编程:ioctl函数

第5章 连接控制:学习stty

终端拥有一个可以让进程读取字符的键盘和让进程发送字符的显示器。终端是一个设备,所以它在目录树中表现为一个特殊的文件,通常在/dev这个目录中。进程和终端间的数据传输和处理由终端驱动程序负责,终端驱动程序是内核的一部分。程序可以通过调用tcgetattr和tcsetattr查看和修改驱动程序设置。