本文主要是介绍unix下C标准文件操作及进程相关知识,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
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命令只是用来终止进程你的,而僵尸进程已经种植呢了。
注释:(
)
每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端,事实上每个终端设备都对应一个不同的设备文件,/dev/tty提供了一个通用的接口,一个进程要访问它的控制终端既可以通过/dev/tty也可以通过该终端设备对应的设备文件来访问,ttyname函数可以由文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备,而不是人任意的文件,
这篇关于unix下C标准文件操作及进程相关知识的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!