阻塞和非阻塞
阻塞和非阻塞这是指的的对象实际上是进程。由于多个进程在并发执行共享系统资源,致使它们在运行过程中呈现出间断性的运行规律,所以进程在其生命周期中可能会具有多种状态。一般而言,每个进程只至少对应以下三种转态的其中一种:
(1)就绪状态(Ready):是指进程已处于准备好运行的状态,即进程已分配到除CPU以外的所有必要资源,只要拿到CPU就可以马上执行。如果系统中存在许多处于“就绪状态”的进程,通常将它们按一定的策略(如优先级策略)排成一个队列,称该队列为就绪队列。
(2)执行状态(Running):是指进程已获得CPU,其程序正在执行的状态。对任何一个时刻而言,在单机处理机系统中,只有一个进程处于执行状态,而在多处理机系统中,则可以有多个进程处于执行状态。
(3)阻塞状态(Block):是指正在执行的进程由于发生了某些事情(如:I/O请求、申请缓冲区失败等)暂时无法继续运行的状态,即进程的执行受到阻塞。此时,为了提高操作系统的效率,操作系统会将处理机分配给另一个就绪进程,而让受阻的进程处于暂停状态,一般将这种暂停状态称为阻塞状态。通常系统会将处于阻塞状态下的进程也排成一个队列,称为阻塞队列。实际上在一般的操作系统中,会按阻塞的原因不同,而设置多个阻塞队列,以提高操作系统的效率。
三种状态的转换
程序:从终端上读取信息(阻塞)
在写这个程序之前,首先要知道如何从一个终端上读写信息。向终端中写信息很简单,直接printf(),就可以直接将信息输出到信息。这是为什么那?在上一篇文章()中说过,一个程序在运行时会默认打开三个流(标准输入,标准输出,标准错误),那么在程序运行时如果遇到printf()这样操作那么就会向标准输出中写,这样输入的内容就会显示在屏幕上。同时,如果程序需要读数据,那么就读入标准输入流中。所有我们可以直接在标准输入中读取终端的信息,将它写在标准输出流中,就会显示在屏幕上了。
STDIN_FILENO 0 //标准输入流文件描述符
STDOUT_FILENO 1 //标准输出流文件描述符
STDERR_FILENO 2 //标准错误流文件描述符
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
//要保证开的缓冲区是可以装下输入的全部字符,不然就会出错。
char buf[100];
int len=read(STDIN_FILENO,buf,100);
if(len<0)
{
perror("read");
exit(1);
}
write(STDOUT_FILENO,buf,len);
return 0;
}
如果运行了程序而不输入数据,那么程序就会一直等待,这就被称为阻塞。
通过ps -aux命令,我们查看一下这个程序的运行状态。(S为睡眠状态)
如果在终端上一直没有内容输入,那终端就会一直在哪里被阻塞,什么也不能干?这可是非常不好的,那如何解决这个问题那,那就需要在文件被打开是给文件指定O_NONBLOCK标志。这样如果终端没有数据读,read/write就是直接返回-1,同时将错误类型设置为EWOULDBLOCK或者EAGAIN(意思为“再试一次“)。如果应用到服务器上这种方式被称为”轮询模型(Poll)“(就是服务器按顺序将所有的客户端都询问一遍)。
程序:修改刚才的代码,使用O_NONBLOCK标志。
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int fileDes=open("/dev/tty",O_RDWR|O_NONBLOCK);
if(fileDes<0)
{
perror("/dev/tty");
}
char buf[10];
int i=0;
for(i=0;i<5;++i)
{
int len=read(fileDes,buf,10);
//如果没有检测到用户输入,就等一秒。
if(len<0&&errno==EWOULDBLOCK)
{
printf("Please Inout:\n");
sleep(1);
continue;
}
else if(len<0)
{
perror("Error Tip:");
}
else
{
write(STDOUT_FILENO,buf,len);
break;
}
}
if(i==5)
{
char buf[]="Time out\n";
write(STDOUT_FILENO,buf,sizeof(buf));
}
close(fileDes);
return 0;
}
lseek
这个函数的作用是移动鼠标的位置和fseek()的功能是一样的。
off_t lseek(int fileDes,off_t offset,int whencn);
参数说明:
(1)fileDes:文件描述符。
(2)offset:偏移量。(相对相对位置的偏移量)
(3)whencn:相对位置。SEEK_SET(文件起始位置),SEEK_CUR(当前鼠标位置),SEEK_END(文件末尾位置)
(4)返回值:成功返回当前鼠标位置,失败返回-1.
程序:利用lseek()函数开辟一个4096K的文件。
使用lseek开辟的空间必须进行一次写操作,负责不会生效。
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
int main()
{
//注意文件的权限位。
umask(0);
int fileDes=open("testFile1",O_RDWR|O_CREAT|O_EXCL,0777);
if(fileDes<0)
{
perror("testFile1");
}
int off=lseek(fileDes,0x1000,SEEK_SET);
//使用lseek开辟的空间必须进行一次写操作,负责不会生效。
char buf[]="";
int len=write(fileDes,buf,sizeof(buf));
if(len<0)
{
perror("write");
}
close(fileDes);
return 0;
}
fcntl
有细心的同学可能注意到在第二次对终端操作的时候,我们没有使用STDIN_FILENO,而是打开/dev/tty文件,这是为什么那?因为文件的标志是要在文件打开时被赋予的,而STDIN_FILENO在程序一开始就自动打开了,所以我们需要使用/dev/tty(表示当前终端)。哪有什么办法可以给打开的文件设置权限位吗?就是使用fnctl()函数。
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
fcntl()函数位可变参函数。
int fcntl(fileDes,F_GETFL); //获取文件的标识位
int fcntl(fileDes,F_SETFL); //设置文件的标识位
程序:修改非阻塞终端读写函数(使用fcntl()函数)
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
//int fileDes=open("/dev/tty",O_RDWR|O_NONBLOCK);
int fileDes=fcntl(STDIN_FILENO,F_GETFL);
fileDes|=O_NONBLOCK;
if(fcntl(STDIN_FILENO,F_SETFL,fileDes)==-1)
{
perror("STNIN_FILENNO");
exit(1);
}
char buf[10];
int i=0;
for(i=0;i<5;++i)
{
//int len=read(fileDes,buf,10);
int len =read(STDIN_FILENO,buf,10);
//如果没有检测到用户输入,就等一秒。
if(len<0&&errno==EWOULDBLOCK)
{
printf("Please Inout:\n");
sleep(1);
continue;
}
else if(len<0)
{
perror("Error Tip:");
}
else
{
write(STDOUT_FILENO,buf,len);
break;
}
}
if(i==5)
{
char buf[]="Time out\n";
write(STDOUT_FILENO,buf,sizeof(buf));
}
close(fileDes);
return 0;
}
ioctl
用来获取、设置设备的物理信息。
程序:获取当前设备的屏幕分辨率。
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <linux/fb.h>
int main()
{
//显示器对应的设备文件为fb0
int fileDes;
struct fb_var_screeninfo screen_info;
fileDes=open("/dev/fb0",O_RDWR);
ioctl(fileDes,FBIOGET_VSCREENINFO,&screen_info);
printf("%d*%d\n",screen_info.xres,screen_info.yres);
return 0;
}
800*600为系统默认屏幕(字符界面)分辨率,而非图像化界面的分辨率。