unix下C标准文件操作及进程相关知识

来源:互联网 发布:网络变压器接线图 编辑:程序博客网 时间:2024/05/26 08:43

close函数可以关闭一个已打开的文件

#include<unistd.h>

int close(int fd);//返回值:若成功返回0;若出错,返回-1并设置errno

 

 

参数fd是要关闭的文件描述符,需要说明的是,当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close(fd)函数关闭,所以即便用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件,但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述符一定要记得关闭,否则随着文件打开的越来越多,会占用大量文件描述符和系统资源。

 

有open函数返回的文件描述符一定是该进程尚未使用的最小描述符,由于程序启动时自动打开文件描述符0.1.2,因此第一次调用open打开文件通常会返回描述符3,再调用open就会返回4,可以利用这一点在标准输入,标准输出,或标准错误输出上打开一个新文件,实现重定向的功能,例如,首先调用close关闭文件描述符1,然后调用open打开一个常规文件,则一定会返回文件描述符1,这时标准输出就不再是终端,而是一个常规文件了,再调用printf就不会打印到屏幕上,而是写到这个文件中了。

 

读常规文件是不会阻塞的,不管读多少字节,read一定会在有限时间内返回,但是从终端设备或网络读则不一定,如果从终端输入的数据没有换行,调用read读终端设备时就会阻塞,如果网络上没有接收到数据包,调用read从网络上读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在哪里,写常规文件是不会阻塞的,而向终端或网络设备写则不一定,。

 

 

fcntl

先前我们以read终端设备为例介绍了非阻塞的I|o,为什么我们不直接对STDIN_FILENO做非阻塞read而是要重新open以便/dev/tty。因为STDIN_FILENO在程序启动时已经被自动打开了,而且我们需要在调用open时指定O_NONBLOCK标志,这里介绍另外一种办法,可以用fcntl函数改变一个已打开的文件的属性,可以重新设置读、写、追加、非阻塞等标志(这些标志称为File Status Flag),而不必重新open文件

函数原型为

#include<unistd.h>

#include<fcntl.h>

 

 

int fcntl<int fd,int cmd>;

int fcntl(int fd,int cmd,long arg);

int fcntl(int fd,int cmd,struct flock*lock);

这个函数和open一样,使用可变参数来实现的,可变参数的类型和个数取决于前面的cmd参数,下面的例子使用F_GETFL和F_SETFL这两种fcntl命令改变STDIN_FILENO的属性,。

 

ioctl用于向设备发控制和配置命令,有些命令也需要读写一些数据,但是这些数据是不能用read|write读写的,称为out-of-band数据,也就是说,read|write读写的数据是in-band数据,是I|O操作的主体,而ioctl命令传送的是控制信息,其中的数据是辅助的数据,例如,在串口线上收发数据通过read|write操作,而串口的波特率、校验位、停止位通过ioctl设置,A|D转换的结果是通过read读取,而A|D转换的精度和工作的频率通过ioctl设置。

其函数原型为:

#include<sys/ioctl.h>

int ioctl(int d,int request,.....);

d是某个设备的文件描述符,request是ioctl的命令,可变参数取决于request,通常是一个指向变量或结构体的指针,若出错则返回-1,若成功则返回其他值,返回值取决于request

 

我们知道,每个进程在内核内都有进程控制块(pcb)来维护进程相关的信息,linux内核的进程控制块是task_struct结构体,现在我们来了解其中有哪些信息。

可以形象地描述为

{

进程id,系统中每一个进程都有唯一的id。在C语言中用pid_t类型来表示,其实就是一个非负的整数。

进程的状态,有运行、挂起、停止、僵尸等。

进程切换时需要保存和恢复一些cpu寄存器。

描述虚拟地址空间的信息。

描述控制终端的信息。

当前工作目录

umask掩码

文件描述符表,包括很多指向file结构体的指针

和信号有关的信息

用户id和组id

控制终端、Session和进程组

进程可以使用的资源上限

}

 

环境变量

先前讲过,exec系统调用执行新程序时会把命令行参数和环境变量表传递给main函数,它们在整个进程地址空间中的位置如下图所示

 

和命令行参数argv类似,环境变量表也是一组字符串。libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时要用extern声明。

 

PATH可执行文件的搜索路径,ls也是一个程序,执行它时 不需要提供完整的路径名/bin/ls。然而通常我们执行当前目录下的程序a.out却需要提供完整的路径名./a.out,这是因为PATH环境变量的值里面包含有ls命令所在的目录/bin.却不包含a.out所在的目录,PATH环境变量的值可以包含多个目录,用:号隔开

 

父进程在创建子进程时会复制一份环境变量给子进程,但是此后两者之间的环境变量互不影响

 

 

 

fork函数

一个现有进程可以调用fork函数创建一个新进程

 

#include<unistd.h>

pid_t fork(void);

由fork创建的新进程被称为子进程,fork函数被调用一次,但返回两次,两次返回的唯一区别是子进程的返回值为0,而父进程的返回值为新子进程的进程ID

 

 

下面介绍一个例子

*********************************************************

char*message;

int n;

pid=fork();

if(pid<0)

{

perror("fork failed");

exit(1);

}

if(pid==0)

{

message="this is the child\n";

n=6;

}

else

{

message="this is the parent\n";

n=3;

}

for(;n>0;n--)

{

printf(message);

sleep(1);

}

**************************************************************

fork调用把父进程的数据复制一份给子进程,但此后;两者互不影响,在这个例子中,fork调用之后父进程和子进程的变量message和n被赋值不同的值,互不影响(子进程只是得到父进程的副本,并不共享存储空间。)

 

}

}

 

 

wait和waitpid函数

 

一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但是它的PCB还保留着,内核在其中的保存了一些信息,但是他的PCB仍然会保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的具体信号,这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底的清除掉这个信息,我们知道一个进程的退出状态可以再shell中用特殊变量查看,因为shell是它的父进程,当它终止时shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程,

如果一个进程已经终止,但是它的父进程尚未调用wait或waitpid对它进行清理,这时的进程状态被称为僵尸进程,任何进程在刚终止时都是僵尸进程,在正常情况下,僵尸进程都是立即被父进程清理的。为了观察僵尸进程,我们自己写一个不正常的程序,父进程fork出子进程,子进程终止,而父进程既不终止也不调用wait清理子进程

 

 

僵尸进程不能用kill命令来清除掉的,因为kill命令只是用来终止进程你的,而僵尸进程已经种植呢了。

 

 

注释:(

libc是Linux下的ANSI C的函数库。ANSI C是基本的C语言函数库,包含了C语言最基本的库函数。这个库可以根据 头文件划分为 15 个部分,其中包括:字符类型 ()、错误码 ()、 浮点常数 ()、数学常数 ()、标准定义 ()、 标准 I/O ()、工具函数 ()、字符串操作 ()、 时间和日期 ()、可变参数表 ()、信号 ()、 非局部跳转 ()、本地信息 ()、程序断言 () 等等。这在其他的C语言的IDE中都是有的。

 

 

每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端,事实上每个终端设备都对应一个不同的设备文件,/dev/tty提供了一个通用的接口,一个进程要访问它的控制终端既可以通过/dev/tty也可以通过该终端设备对应的设备文件来访问,ttyname函数可以由文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备,而不是人任意的文件,

 

 

 

 

 

 

0 0