函数dup与dup2

函数原型

1
2
3
4
#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);

函数功能

这两个函数的功能都是用来复制一个现有的文件描述符。返回的文件描述符与原有的文件描述符共用同一个文件表项,但是文件描述符标志将被清除,即当进程调用exec时文件描述符将不会被关闭。

返回值

dup返回当前可用的最小的文件描述符。dup2返回fd2,若fd2所表示的文件已经打开,则将该文件描述符先关闭,然后再将fd复制到fd2上返回。若fd2与fd相同,则直接返回fd、.

等效函数

dup函数与fcntl的F_DUPFD功能相同。

dup(fd)等效于fcntl(fd, F_DUPFD, 0)

dup2(fd, fd2)等效于close(fd), fcntl(fd, F_DUPFD, fd2)

但是dup2是原子的上述先关闭然后在复制不是原子操作。

作用及用途

dup函数常用来重定向进程的stdin、stdout以及stderr。原理与CGI类似,即将标准输入标准输出重定向到一个文件或者socket流等。

重定向标准输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
int fd = dup(STDIN_FILENO);
if (fd < 0) {
printf("dup error\n");
exit(1);
}
char buf[] = "this is a test of dup\n";
write(fd, buf, sizeof(buf));
return 0;
}

Comment and share

CGI原理

CGI(Common Gateway Interface)通用网关接口,CGI描述了服务器和请求处理程序之间传输数据局的一种标准。在理解的过程中我们需要区分CGI和CGI程序,CGI是一种数据传输的标准,而CGI程序是实际处理业务的一个程序。webserver每请求一次,CGI程序就会fork出一个子进程进行处理。CGI程序的参数通过环境变量和标准输入获得,它的相应通过标准输出传递给webServer。

CGI的工作原理是:

  • 客户端通过http将请求发送到web服务器
  • web服务器接收并收集用户请求,然后交给CGI程序进行处理
  • CGI程序把处理后的结果发送给服务器
  • 服务器将结果传送给浏览器

其中上述第二步web服务器通过环境变量或标准输入将请求发送给CGI程序,第三步CGI通过标准输出将结果发送给webServer

CGI接口标准:标准输入,环境变量以及标准输出

借口标准 介绍
标准输入 CGI程序像其他可执行程序一样,可通过标准输入(stdin)从Web服务器得到输入信息,如Form中的数据,这就是所谓的向CGI程序传递数据的POST方法。这意味着在操作系统命令行状态可执行CGI程序,对CGI程序进行调试。POST方法是常用的方法。
环境变量 操作系统提供了许多环境变量,它们定义了程序的执行环境,应用程序可以存取它们。Web服务器和CGI接口又另外设置了自己的一些环境变量,用来向CGI程序传递一些重要的参数。CGI的GET方法还通过环境变量QUERY-STRING向CGI程序传递Form中的数据。
标准输出 CGI程序通过标准输出(stdout)将输出信息传送给Web服务器。传送给Web服务器的信息可以用各种格式,通常是以纯文本或者HTML文本的形式,这样我们就可以在命令行状态调试CGI程序,并且得到它们的输出。

###常用的环境变量
Linux中的环境变量是一系列的键值对集合,它们的值可以通过shell设置,也可以被其他进程或程序设置和访问,它们是web服务器传递给CGI程序的一种最简单的方式,之所以叫做环境变量是因为它们是全局变量,任何程序都可以存取它们。

key 意义
SERVER_NAME CGI脚本运行时的主机名和IP地址
SERVER_SOFTWARE 你的服务器的类型如: CERN/3.0 或 NCSA/1.3.
GATEWAY_INTERFACE 运行的CGI版本. 对于UNIX服务器, 这是CGI/1.1
SERVER_PROTOCOL 服务器运行的HTTP协议. 这里当是HTTP/1.0.
SERVER_PORT 服务器运行的TCP口,通常Web服务器是80.
REQUEST_METHOD POST 或 GET, 取决于你的表单是怎样递交的
HTTP_ACCEPT 浏览器能直接接收的Content-types, 可以有HTTP Accept header定义.
HTTP_USER_AGENT 递交表单的浏览器的名称、版本 和其他平台性的附加信息。
HTTP_REFERER 递交表单的文本的 URL,不是所有的浏览器都发出这个信息,不要依赖它
PATH_INFO 附加的路径信息, 由浏览器通过GET方法发出.
PATH_TRANSLATED 在PATH_INFO中系统规定的路径信息.
SCRIPT_NAME 指向这个CGI脚本的路径, 是在URL中显示的(如, /cgi-bin/thescript).
QUERY_STRING 脚本参数或者表单输入项(如果是用GET递交). QUERY_STRING包含URL中问号后面的参数
REMOTE_HOST 递交脚本的主机名,这个值不能被设置.
REMOTE_ADDR 递交脚本的主机IP地址.
REMOTE_USER 递交脚本的用户名. 如果服务器的authentication被激活,这个值可以设置。
REMOTE_IDENT 如果Web服务器是在ident (一种确认用户连接你的协议)运行, 递交表单的系统也在运行ident, 这个变量就含有ident返回值.
CONTENT_TYPE 如果表单是用POST递交, 这个值将是 application/x-www-form-urlencoded. 在上载文件的表单中, content-type 是个 multipart/form-data.
CONTENT_LENGTH 对于用POST递交的表单,标准输入口的字节数.

CGI的工作原理图

Comment and share

Wait函数详解

kill

头文件

sys/types.h
signal.h

函数功能

注意此函数的功能是向指定进程发送信号。而不是杀死某个进程.名字为kill的原因是早期的Unix系统对信号的默认处理方式大部分是终止进程。

函数原型

int kill(pid_t pid, int sig);

返回值:执行成功返回0,执行失败返回-1。

参数说明

pid为进程ID,sig标识要发送的信号。

kill()函数的作用是用来向指定的进程或进程组发送信号。其中pid有一下
几种类型:

  • pid > 0: 发送信号给进程标识为pid的进程
  • pid = 0: 发送信号给当前进程相同进程组的所有进程
  • pid = -1:发送信号给系统内除了1号进程以外的所有进程
  • pid < -1:发送信号给进程组标识为-pid的进程。

当sig=0时没有信号发出,但是系统会执行错误检查,通常会利用sig值为0来检查某个进程是否在执行。

  • 若进程不存在,errno为ESRCH
  • 若errno为EPERM(无权向目标进程发送信号,但是存在)或调用成功,表示进程存在。

Example

相关知识:WIFSIGNALED和WIFEXITED两个宏是用来判断当前程序的子进程的退出方式,是接收到信号异常退出
还是正常调用exit()或return退出。

  • WIFEXITED: 调用exit()或return退出
  • WIFSIGNALED:接收到信号异常退出 此时可以使用WTERMSIG(status)获取其接受信号的内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
main()
{
pid_t pid;
int status;
if(!(pid= fork()))
{
printf("Hi I am child process!\n");
sleep(10);
return;
}
else
{
printf("send signal to child process (%d) \n", pid);
sleep(1);
kill(pid, SIGABRT);
wait(&status);
if(WIFSIGNALED(status))
printf("chile process receive signal %d\n", WTERMSIG(status));
}
}

raise和killpg函数

除了kill函数可以向进程发送信号外,还可以通过raise和killpg函数发送信号。

函数原型

int raise(int sig);
int killpg(pid_t pgrpid, int sig);

函数功能

raise函数的功能是向进程自身发送信号.在单线程程序中,调用raise函数相当于调用kill(getpid(), sig).
对于支持线程的系统,一般将raise实现为:

1
pthread_kill(pthread_self(), sig);

此时信号只发送给当前进程的当前线程。而不影响其他线程。

killpg函数的作用是向同组的所有进程发送信号。相当于:

1
kill(-pgrpid, sig);

函数返回值

raise函数的错误返回值只有一个,即EINVAL,表示信号无效。其他时候返回都为成功。
而killpg的返回值与kill类似。

wait()与waitpid()

头文件

sys/wait.h

函数原型

1
2
pid_t wait(int* status);
pid_t waitpid(pid_t pid, int* status, int options);

函数说明

父进程创建子进程之后,父进程使用wait和waitpid具有监视子进程的运行状态的能力。这两个函数用于等待子进程的状态发生变化回调并且获取状态发生变化的信息,所能获取的状态变化包括:子进程运行结束,子进程被信号量暂停,子进程被信号量回复运行

父进程执行wait函数之后,父进程会被阻塞在此处,如果子进程状态发生变化,则wait函数会立即返回结果;否则wait函数会一直阻塞直到子进程状态发生变化。

通常意义上,,如果子进程状态发生了变化,但是还是未被其父进程或者其他系统回调执行wait函数,此时的子进程被称为可等待的。

子进程运行结束后父进程执行wait函数可以推动系统释放与子进程相关的资源;否则子进程将会被维持在僵尸进程(子进程已结束,而父进程还在运行)的状态下一直存在。

返回值说明

上述函数的返回值有-1,0,>0三种情况。分别对应于以下三种情况返回。

  • -1:调用出错,此时出错信息在errno中
  • 0:若waitpid的options设置了WNOHANG,且调用中没有子进程退出,立即返回0
  • >0:若大于0,返回退出进程的pid。

参数说明

  • pid:要监听的进程的ID(<-1, =-1, =0, > 0)
  • status: 用于存储出发状态变化时的信号值和exit(code)中的code值。
  • options 提供一些额外的选项控制waitpid,目前linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数宏,可以使用|连接使用.

pid详细说明:

  • pid < -1 : 监听范围为进程组为-pid的所有子进程
  • pid = -1 : 监听范围为调用wait/waitpid的所有子进程
  • pid = 0: 监听范围为子进程的进程组ID(与父进程相等)
  • pid > 0: 监听特定pid的进程

status详细说明

status用于保存出发wait的信号值或者退出时exit(code)中的code值

options详细说明

  • WNOHANG : 使用此参数调用waitpid,即使子进程没有退出,他也会立即返回,而不是像wait一直等下去
  • WUNTRACED : 用于调试,极少用

一般情况下使用值为0即可。

wait与waitpid关系

wait实质上是waitpid中pid=-1,options=0时封装,即

wait(&status)与waitpid(-1, &status, 0)完全相同

相关宏

wait.h中定义了一些宏用于解析status的值:

含义
WIFEXITED(status) 子进程正常退出返回true否则false
WEXITSTATUS(status) 当正常退出时,返回exit(code)中的code
WIFSIGNALED 子进程接受信号退出时返回true,否则false
WTERMSIG 被信号量杀死时,返回信号量的值
WIFSTOPED(status) 当子进程被信号量暂停时返回true
WSTOPSIG(status) 被信号量暂停时信号量的值

options值:

常量 含义
WNOHANG 调用wait时制定pid仍未返回,wait立即返回0,用于判断子进程有没有结束
WUNTRACED 当子进程被暂停时,则wait立即返回子进程的pid
WCONTINUED 当被暂停的子进程又被信号量恢复后,则wait立即返回子进程的pid。Linux 2.6.10及以后生效。在Mac 0S X 10.9.5上未生效。

Comment and share

Linux用于I/O的数据结构及fcntl函数详解

Linux内核用于IO的数据结构

内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响.

  • 进程表项 每个进程在记录表中都有一个记录项,记录项中包含一张打开的文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
    • 文件描述符标志(close_on_exec,close_on_exec是一个进程所有文件描述符的标记位图,每个比特位代表一个打开的文件描述符,用于确定在系统调用execve()时需要关闭的文件句柄)。
    • 指向一个文件表项的指针
  • 文件表项 内核为所有打开文件维护一张文件表(不同进程打开相同文件将有两条记录),每个文件表项中包括:
    • 文件状态标志(read,write,append,async,nonblock等)
    • 当前文件偏移量
    • 指向该文件v(i)节点表项的指针
  • 节点表项。每个打开的文件都有一个v-node结构,v-node中包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件,V-node中还包含了文件inode节点信息,这些信息是打开文件时从磁盘上读入内存的,所以文件所有信息都是随时可用的。
    • v节点的信息
    • 当前文件的长度
    • i节点的信息

close_on_exec是一个进程所有文件描述符(文件句柄)的位图标志,每个bit代表一个打开的文件描述符,用于
确定在系统调用execve()时是否需要关闭文件句柄。
当一个进程fork出一个子进程时,通常会在子进程中调用execve()函数
加载执行另一个新程序。此时子进程将完全被新程序替换掉,并在子进程中执行新程序。若一个文件描述符在close_on_exec中对应的
bit被设置,那么在执行execve()时该文件描述符将被关闭,否则该文件描述符将始终处于打开状态。
当打开一个文件的时候,默认情况下文件句柄在子进程中也处于打开状态。
注意文件描述符和文件描述符标志的区别,文件描述符是文件进程打开文件时的文件句柄,文件描述符标志为close_on_exec。

下图显示了一个进程打开两个不同文件时三张表对应的关系:

如果两个独立的进程同时打开同一个文件,三张表之间的对应关系如下:


从上面可以看出,不同进程打开相同的文件时每个进程将获得各自的文件表项,这是因为不同的进程都有各自的文件偏移量。
当我们对文件进行操作的时候,上面三种表项之间的变化关系如下:

  • 当对文件进行写操作时(write),在文件表项中的文件偏移量将增加写入的字节数。如果此时文件偏移量超过了文件长度,更新文件长度为当前的文件偏移量
  • 当用O_APPEND标志打开一个文件,则相应的标志也被设置到文件表项的文件标志状态中。每次对这种具有追加标志的文件进行写操作时,首先将当前文件偏移量设置为文件文件长度,这就使得每次增加的内容都会写到文件末尾。
  • 若使用lseek定位到文件末尾,则文件表项中偏移量被设置为文件长度
  • lseek函数只修改文件表项中的偏移量,不进行任何IO操作。

注意问题:

  • 可能有多个文件描述符指向同一个文件表项,如在fork的时候就有可能发生
  • 注意文件描述符和文件状态标志在作用范围方面的区别。前者只用于一个进程的描述符,而后者则应用于指向该给定文件表项的任何进程中的所有描述符。

函数fcntl功能及用法

函数原型:

1
2
3
#include <fcntl.h>
int fcntl(int fd, int cmd, .../*int args or lock args*/);

函数功能:
fcntl的作用是改变已经打开的文件属性。

参数说明:

  • fd 为file descriptor,即文件打开之后的文件描述符
  • cmd为命令,即需要对fd操作的命令,一般为几个宏定义中的其中一个
  • args 参数,此参数为执行cmd命令所需要的参数

cmd参数命令及功能:

fcntl的功能可以分为5种:

  • 复制一个已有的文件描述符
    • cmd = F_DUPFD,此功能*返回一个文件描述符,新的描述符的值为大于或等于args的可用的(尚未打开)文件描述符的最小值,新描述符与fd共用一个文件表项。但是新的文件描述符有它自己的一套文件描述符标志.
    • cmd = F_DUPFD_CLOEXEC.与上述功能一致,唯一不同的是使用此命令会设置CLOSE_ON_EXEC,
      即当执行execve的时候,文件描述符将被关闭。
  • 获取或设置文件描述符标志
    • cmd = F_GETFD 返回与fd关联的close_on_exec标志,第三个参数被忽略。
    • cnd = F_SETFD 将文件描述符标志close_on_exec设置为第三个参数
  • 获取或设置文件状态标志
    • cmd = F_GETFL 获取fd对应的文件的状态标志(存储于文件表项)
    • cmd = F_SETFL 设置fd对应文件的状态标志
  • 获取或设置异步IO所有权
    • cmd = F_GETOWN 获取当前接受SIGIO和SIGURG信号的进程IO或者进程组ID。
    • cmd = F_SETOWN 设置接受SIGIO和SIGURG信号的进程ID或进程组ID。返回值为正则为进程,返回值为负数即为进程组。
  • 获取或记录锁(cmd=F_GETLK、F_SETLK、F_SETLKW),此处不详解

文件状态标志说明

文件状态标志存储与文件表项中,它用于说明进程对当前文件的可操作权限。文件的
操作权限说明如下图表所示,权限设置可使用|或者&进行设置.

文件状态标志 功能说明
O_RDONLY 只读权限
O_WRONLY 只写权限
O_RDWR 读写权限
O_EXEC 可执行权限
O_SEARCH 只搜索打开权限
O_APPEND 追加写
O_NONBLOCK 非阻塞模式
O_SYNC 等待写完成(数据和属性)
O_DSYNC 等待写完成(仅数据)
O_RSYNC 同步读写
O_FSYNC 等待写完成
O_ASYNC 异步IO

注意O_RDONLY、O_WRONLY、O_RDWR、O_EXEC、O_SEARCH这个五个标志并不各占
一位,一个文件的访问方式只能取这五个里面的一个。因此检查当前文件的是这
五个标志中的哪一个需要使用屏蔽字O_ACCMODE取得当问方式位,在于这五个标志
进行对比。

代码示例说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int test;
//返回结果:test = 10,返回大于等于第三个参数可用的fd(10)。
test = fcntl(STDIN_FILENO, F_DUPFD, 10);
printf("result of fcntl(STDIN_FILENO, F_DUPFD, 10) is:%d\n", test);
//返回结果:test=11,与上一个函数功能相同
//不同之处在于其设置了close_on_exec,当执行exec时关闭响应的文件描述符
//注意有一些版本的系统上没有定义这个宏
//test = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 10);
//返回结果 test = 0. 标准输入的文件描述符标志(即关闭标志)为0,
//意思是执行exec时关联的文件描述符不关闭
test = fcntl(STDIN_FILENO, F_GETFD);
printf("the result of fcntl(STDIN_FILENO, F_GETFD) is:%d\n", test);
//将文件描述符标志设置为0
fcntl(STDIN_FILENO, F_SETFD, 0);
//返回结果 test = 32270
test = fcntl(STDIN_FILENO, F_GETFL);
printf("the result of fcntl(STDIN_FILENO, F_GETFL) is:%d\n", test);
//给STDIN_FILENO对应的IO添加非阻塞权限
test |= O_NONBLOCK;
fcntl(STDIN_FILENO, F_SETFL, test);
test = fcntl(STDIN_FILENO, F_GETFL);
printf("the result of fcntl(STDIN_FILENO, F_GETFL) is:%d\n", test);
}

STDIN_FILENO,STDOUT_FILENO以及STDERR_FILENO

STDIN_FILENO等是系统API接口库中的宏定义,它是一个int类型的值,是打开文件的句柄,
对应的主要函数有open,read,write和close等。
STDIN_FILENO的含义是标准输入(键盘)的文件描述符,STDOUT_FILENO是标准输出流的文件描述符,STDERR_FILENO
是标准错误流的文件描述符。

STDIN_FILENO与stdin的区别

  • 数据类型不同 stdin的数据类型为FILE*,STDIN_NO的数据类型为int
  • 可用的函数不同 stdin主要用的函数有fread,fwrite,fclose,STDIN_FILENO可用的函数为write,read和close
  • stdin属于标准IO,高级的输入输出函数,在stdio.h中定义;STDIN_FILENO是文件描述符,一般定义为0,1,2,属于没有buffer的IO,直接调用系统调用,定义在unistd.h中
  • 层次不同,stdin属于标注库处理的输入流,其声明为FILE*型,对应的函数前面都有f开头;而STDIN_FILENO属于系统API接口,对用的函数是一些系统级的调用

Comment and share

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China