系统编程部分知识点总结
2012-06-25 16:57
239 查看
1.open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
返回值:成功返回新分配的文件描述符,出错返回-1并设置errno
pathname 参数是要打开或创建的文件名,和fopen一样,pathname 既可以是相对路径也可以是绝对路径。
flags参数有一系列常数值可供选择,可以同时选择多个常数用按位或运算符连接起来,
所以这些常数的宏定义都以O_开头,表示or 。
以下三个三先一:
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 可读可写打开
以下可多选(以或的形式)
O_APPEND
表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
O_CREAT
若此文件不存在则创建它。使用此选项时需要提供第三个参数mode,表示该文件的访问权限。
O_EXCL
如果同时指定了O_CREAT,并且文件已存在,则出错返回。
O_TRUNC
如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断(Truncate )为0字节。
O_NONBLOCK
对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O (Nonblock I/O ),非阻塞I/O
函数与C 标准I/O 库的fopen函数有些细微的区别:
1 以可写的方式fopen一个文件时,如果文件不存在会自动创建,而open一个文件时必须明
确指定O_CREAT才会创建文件,否则文件不存在就出错返回。
2 以w 或w+方式fopen一个文件时,如果文件已存在就截断为0 字节,而open一个文件时必须
明确指定O_TRUNC才会截断文件,否则直接在原来的数据上改写。
第三个参数mode指定文件权限,可以用八进制数表示,比如0644表示-rw-r--r--
实验
1.umask
0022
用touch命令创建一个文件时,创建权限是0666,而touch进程继承了Shell进程的umask掩码,
所以最终的文件权限是0666&~022=0644 。
2.touch aaa.c
ll aaa.c
-rw-r--r-- 1 root root 0 01-30 14:18 aaa.c
同样道理,用gcc 编译生成一个可执行文件时,创建权限是0777,而最终的文件权限
是0777&~022=0755 。
gcc aaa.c
ll a.out
-rwxr-xr-x 1 root root 4943 01-30 14:20 a.out
3.umask 0
再重复上述实验
注:
当文件创建时,mode参数提供新建文件的权限。系统并不在该次打开文件时
检查权限,所以你可以进行相反的操作,例如设置文件为只读权限,但却在
打开文件后进行写操作。
==========================================================
Given a pathname for a file, open() returns a file
descriptor, a small, non-negative integer for use in
subsequent system calls (read(2), write(2), lseek(2),
fcntl(2), etc.). The file descriptor returned by a suc-
cessful call will be the lowest-numbered file descriptor
not currently open for the process.
The parameter flags must include one of the following
access modes: O_RDONLY, O_WRONLY, or O_RDWR. These
request opening the file read-only, write-only, or
read/write, respectively.
In addition, zero or more file creation flags and file
status flags can be bitwise-or’d in flags. The file
creation flags are O_CREAT, O_EXCL, O_NOCTTY, and
O_TRUNC. The file status flags are all of the remaining
flags listed below. The distinction between these two
groups of flags is that the file status flags can be
retrieved and (in some cases) modified using fcntl(2).
The full list of file creation flags and file status
flags is as follows:
O_APPEND
The file is opened in append mode. Before each
write(), the file offset is positioned at the end
of the file, as if with lseek(). O_APPEND may
lead to corrupted files on NFS file systems if
more than one process appends data to a file at
once. This is because NFS does not support
appending to a file, so the client kernel has to
simulate it, which can’t be done without a race
condition.
O_CREAT
If the file does not exist it will be created.
The owner (user ID) of the file is set to the
effective user ID of the process. The group own-
ership (group ID) is set either to the effective
group ID of the process or to the group ID of the
parent directory (depending on filesystem type
and mount options, and the mode of the parent
directory, see, e.g., the mount options bsdgroups
and sysvgroups of the ext2 filesystem, as
described in mount(8)).
O_EXCL
When used with O_CREAT, if the file already
exists it is an error and the open() will fail.
In this context, a symbolic link exists, regard-
less of where it points to. O_EXCL is broken on
NFS file systems; programs which rely on it for
performing locking tasks will contain a race con-
dition. The solution for performing atomic file
locking using a lockfile is to create a unique
file on the same file system (e.g., incorporating
hostname and pid), use link(2) to make a link to
the lockfile. If link() returns 0, the lock is
successful. Otherwise, use stat(2) on the unique
file to check if its link count has increased to
2, in which case the lock is also successful.
O_NONBLOCK or O_NDELAY
When possible, the file is opened in non-blocking
mode. Neither the open() nor any subsequent oper-
ations on the file descriptor which is returned
will cause the calling process to wait. For the
handling of FIFOs (named pipes), see also
fifo(7). For a discussion of the effect of
O_NONBLOCK in conjunction with mandatory file
locks and with file leases, see fcntl(2).
O_TRUNC
If the file already exists and is a regular file
and the open mode allows writing (i.e., is O_RDWR
or O_WRONLY) it will be truncated to length 0.
If the file is a FIFO or terminal device file,
the O_TRUNC flag is ignored. Otherwise the effect
of O_TRUNC is unspecified.
The argument mode specifies the permissions to use in
case a new file is created. It is modified by the pro-
cess’s umask in the usual way: the permissions of the
created file are (mode & ~umask). Note that this mode
only applies to future accesses of the newly created
file; the open() call that creates a read-only file may
well return a read/write file descriptor.
RETURN VALUE
open() and creat() return the new file descriptor, or -1
if an error occurred (in which case, errno is set appro-
priately).
ERRORS
EACCES
The requested access to the file is not allowed,
or search permission is denied for one of the
directories in the path prefix of pathname, or
the file did not exist yet and write access to
the parent directory is not allowed. (See also
path_resolution(2).)
EEXIST
pathname already exists and O_CREAT and O_EXCL
were used.
EFAULT
pathname points outside your accessible address space.
creat() is equivalent to open() with flags equal to
O_CREAT|O_WRONLY|O_TRUNC.
creat("aa.c",0666); open("aa.c",O_CREAT|O_WRONLY|O_TRUNC,0666);
2 creat
OWRONLY | OCREAT | OTRUNC 经常被组合使用。
int creat (const char *name, mode_t mode);
fd = open (file, O_WRONLY | O_CREAT | O_TRUNC,0644);
creat(file,0644);
本质:
int creat (const char *name, int mode)
{
return open (name, O_WRONLY | O_CREAT |O_TRUNC, mode);
}
3.close
#include <unistd.h>
int close(int fd);
返回值:成功返回0 ,出错返回-1并设置errno
参数fd是要关闭的文件描述符。需要说明的是,当一个进程终止时,内核对该进程所有尚未关
闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭
它打开的所有文件。但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述
符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。
====================================================================================
close()
closes a file descriptor, so that it no longer
refers to any file and may be reused. Any record locks
(see fcntl(2)) held on the file it was associated with,
and owned by the process, are removed (regardless of the
file descriptor that was used to obtain the lock).
3作业
1.打开文件/home/akae.txt用于写操作,以追加方式打开.
int fd;
fd = open("/home/akae.txt", O_WRONLY | O_APPEND);
if (fd == -1)
{
perror("open");
exit(1);
}
2.打开文件/home/akae.txt用于写操作,如果该文件不存在则创建它,创建权限为0666.
fd = open("/home/akae.txt", O_WRONLY | O_CREAT,0666);
if (fd == -1)
{
perror("open");
exit(1);
}
3.打开文件/home/akae.txt用于写操作,如果该文件已存在则截断为0 字节,如果该文件不
存在则创建它,创建权限为0666.
fd = open("/home/akae.txt", O_WRONLY|O_CREAT|O_TRUNC,0666);
4.打开文件/home/akae.txt用于写操作,如果该文件已存在则报错退出,如果该文件不存在
则创建它,创建权限为0666.
fd = open("/home/akae.txt", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC,0666);
**************************************************************************************************
1 read函数从打开的设备或文件中读取数据
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文
件末尾,则这次read返回0 ssize_t 是signed int类型
参数count是请求读取的字节数,读上来的数据保存在缓冲区buf 中,同时文件的当前读写位置
向后移。注意返回值类型是ssize_t,表示有符号的ssize_t ,这样既可以返回正的字节数、0
(表示到达文件末尾)也可以返回负值-1 (表示出错)。
read函数返回时,返回值说明了buf 中前多少个字节是刚读上来的。
有些情况下,实际读到的字节数(返回值)会小于请求读的字节数count.
例如:
1,读常规文件时,在读到count个字节之前已到达文件末尾。例如,距文件末尾还有30个字
节而请求读100 个字节,则read返回30,下次read将返回0 。
2,从终端设备读,通常以行为单位,读到换行符就返回了。
3,从网络读,根据不同的传输层协议和内核缓存机制,返回值可能小于请求的字节数,后
面socket 编程部分会详细讲
===========================================================================================
DESCRIPTION
read() attempts to read up to count bytes from file
descriptor fd into the buffer starting at buf.
RETURN VALUE
On success, the number of bytes read is returned (zero
indicates end of file), and the file position is
advanced by this number. It is not an error if this
number is smaller than the number of bytes requested;
this may happen for example because fewer bytes are
actually available right now (maybe because we were
close to end-of-file, or because we are reading from a
pipe, or from a terminal), or because read() was inter-
rupted by a signal. On error, -1 is returned, and errno
is set appropriately.
ERRORS
EAGAIN
Non-blocking I/O has been selected using O_NON-BLOCK
and no data was immediately available forreading.
EINTR
The call was interrupted by a signal before any data was read.
2.write函数向打开的设备或文件中写数据
SYNOPSIS
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
返回值:成功返回写入的字节数,出错返回-1并设置errno
写常规文件时,write的返回值通常等于请求写的字节数count,而向终端设备或网络写则不一
定。
读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或
网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网
络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如
果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网
络写则不一定。
====================================================================
DESCRIPTION
write() writes up to count bytes to the file referenced
by the file descriptor fd from the buffer starting at
buf. POSIX requires that a read() which can be proved
to occur after a write() has returned returns the new
data. Note that not all file systems are POSIX conform-
ing.
RETURN VALUE
On success, the number of bytes written are returned
(zero indicates nothing was written). On error, -1 is
returned, and errno is set appropriately. If count is
zero and the file descriptor refers to a regular file, 0
may be returned, or an error could be detected. For a
special file, the results are not portable.
ERRORS
EAGAIN
Non-blocking I/O has been selected using O_NON-
BLOCK and the write would block.
EINTR
The call was interrupted by a signal before any
data was written.
补充:
现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠
(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收
到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运
行(Running )状态,在Linux内核中,处于运行状态的进程分为两种情况:
1. 正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip )里保存着该进程
的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,
正在读写该进程的地址空间。
2. 就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一
个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进
程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进
程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,
同时要兼顾用户体验,不能让和用户交互的进程响应太慢。
*********************************************************************************************************
1
读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或
网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网
络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如
果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网
络写则不一定。
现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠
(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收
到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运
行(Running )状态,在Linux内核中,处于运行状态的进程分为两种情况:
1 正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip )里保存着该进程
的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,
正在读写该进程的地址空间。
2 就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一
个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进
程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进
程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,
同时要兼顾用户体验,不能让和用户交互的进程响应太慢。
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
int main()
{
int n;
char buf[10];
n = read(STDIN_FILENO,buf,10);
if(n<0)
{
perror("stdin_fileno") ;
exit(1);
}
write(STDOUT_FILENO,buf,n);
return 0;
}
解释:
1 Shell进程创建a.out进程,a.out进程开始执行,而Shell进程睡眠等待a.out进程退出。
2 a.out调用read时睡眠等待,直到终端设备输入了换行符才从read返回,read只读走10个
字符,剩下的字符仍然保存在内核的终端设备输入缓冲区中。
3 a.out进程打印并退出,这时Shell进程恢复运行,Shell继续从终端读取用户输入的命令,
于是读走了终端设备输入缓冲区中剩下的字符d 和换行符,把它当成一条命令解释执行,
结果发现执行不了,没有d 这个命令。
2.如何轮询读取多个设备呢?
如果在open一个设备时指定了O_NONBLOCK标志,read/write就不会阻塞。以read为例,如果设备
暂时没有数据可读就返回-1 ,同时置errno为EWOULDBLOCK (或者EAGAIN ,这两个宏定义的值相
同),表示本来应该阻塞在这里(would block,虚拟语气),事实上并没有阻塞而是直接返回
错误,调用者应该试着再读一次(again)。这种行为方式称为轮询(Poll ),调用者只是查询
一下,而不是阻塞在这里死等,这样可以同时监视多个设备:
模型1:
while(1)
{
非阻塞read(设备1);
if(设备1 有数据到达)
处理数据;
非阻塞read(设备2);
if(设备2 有数据到达)
处理数据;
}
非阻塞I/O 有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如
果阻塞在那里,操作系统可以调度别的进程执行,就不会做无用功了。在使用非阻塞I/O 时,通
常不会在一个while循环中一直不停地查询(这称为Tight Loop ),而是每延迟等待一会儿来查
询一下,以免做太多无用功,在延迟等待的时候可以调度其它进程执行
所以----
模型2:
while(1)
{
非阻塞read(设备1);
if(设备1 有数据到达)
处理数据;
非阻塞read(设备2);
if(设备2 有数据到达)
处理数据;
sleep(n);
}
以下是一个非阻塞I/O 的例子。目前我们学过的可能引起阻塞的设备只有终端,所以我们用终端
来做这个实验。程序开始执行时在0 、1 、2 文件描述符上自动打开的文件就是终端,但是没
有O_NONBLOCK标志
读标准输入是阻塞的。我们可以重新
打开一遍设备文件/dev/tty (表示当前终端),在打开时指定O_NONBLOCK标志。
int main()
{
close(0);
int fd;
fd = open("/dev/tty",O_RDONLY|O_NONBLOCK);
printf("fd is %d\n",fd);
char buf[10];
int n;
tryagain:
n = read(fd,buf,10);
// printf("n = %d\n",n);
if(n < 0)
{
if (errno == EAGAIN)
{
sleep(1);
write(2,"try again\n",10);
goto tryagain;
}
perror("read");
exit(1);
}
write(2,buf,n);
close(fd);
}
超时退出示例:
int main()
{
close(STDIN_FILENO);
char buf[10];
int fd;
int n;
fd = open("/dev/tty",O_RDONLY|O_NONBLOCK);
printf("fd=%d\n",fd);
int count = 0;
while(1)
{
n = read(0,buf,10);
//printf("n = %d\n",n);
if(n>=0)
break;
if(n<0)
{
if(errno == EAGAIN)
{
printf("try again\n");
sleep(1);
count++;
if(count == 5)
break;
continue;
}
perror("read");
exit(1);
}
}
if(count == 5)
{
printf("time out!\n");
}
else
{
write(1,buf,n);
}
return 0;
}
3.select
重点讲解
SYNOPSIS
/* According to POSIX.1-2001 */
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
DESCRIPTION
select() allow a program to monitor multiple file
descriptors, waiting until one or more of the file descriptors become
"ready" for some class of I/O operation (e.g., input possible). A file
descriptor is considered ready if it is possible to perform the corre-
sponding I/O operation (e.g., read(2)) without blocking.
Three independent sets of file descriptors are watched. Those listed
in readfds will be watched to see if characters become available for
reading (more precisely, to see if a read will not block; in particu-
lar, a file descriptor is also ready on end-of-file), those in writefds
will be watched to see if a write will not block, and those in
exceptfds will be watched for exceptions. On exit, the sets are modi-
fied in place to indicate which file descriptors actually changed sta-
tus. Each of the three file descriptor sets may be specified as NULL
if no file descriptors are to be watched for the corresponding class of
events.
Four macros are provided to manipulate the sets. FD_ZERO() clears a
set. FD_SET() and FD_CLR() respectively add and remove a given file
descriptor from a set. FD_ISSET() tests to see if a file descriptor is
part of the set; this is useful after select() returns.
nfds is the highest-numbered file descriptor in any of the three sets,
plus 1.
timeout is an upper bound on the amount of time elapsed before select()
returns. It may be zero, causing select() to return immediately. (This
is useful for polling.) If timeout is NULL (no timeout), select() can
block indefinitely.
The timeout
The time structures involved are defined in <sys/time.h> and look like
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
On Linux, select() modifies timeout to reflect the amount of time not
slept; most other implementations do not do this. (POSIX.1-2001 per-
mits either behaviour.) This causes problems both when Linux code
which reads timeout is ported to other operating systems, and when code
is ported to Linux that reuses a struct timeval for multiple select()s
in a loop without reinitializing it. Consider timeout to be undefined
after select() returns.
RETURN VALUE
On success, select() and pselect() return the number of file descrip-
tors contained in the three returned descriptor sets (that is, the
total number of bits that are set in readfds, writefds, exceptfds)
which may be zero if the timeout expires before anything interesting
happens. On error, -1 is returned, and errno is set appropriately; the
sets and timeout become undefined, so do not rely on their contents
after an error.
EXAMPLE
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
fd_set rfds;
struct timeval tv;
int retval;
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don’t rely on the value of tv now! */
if (retval == -1)
perror("select()");
else if (retval)
printf("Data is available now.\n");
/* FD_ISSET(0, &rfds) will be true. */
else
printf("No data within five seconds.\n");
return 0;
}
****************************************************
1 lseek
每个打开的文件都记录着当前读写位置,打开文件时读写位置是0 ,表示文件开头,通常读写多
少个字节就会将读写位置往后移多少个字节。但是有一个例外,如果以O_APPEND 方式打开,每
次写操作都会在文件末尾追加数据,然后将读写位置移到新的文件末尾。lseek和标准I/O 库
的fseek函数类似,可以移动当前读写位置(或者叫偏移量)。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数offset 和whence 的含义和fseek函数完全相同。只不过第一个参数换成了文件描述符。
和fseek一样,偏移量允许超过文件末尾,这种情况下对该文件的下一次写操作将延长文件,中
间空洞的部分读出来都是0 。
若lseek成功执行,则返回新的偏移量,因此可用以下方法确定一个打开文件的当前偏移量:
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);
这种方法也可用来确定文件或设备是否可以设置偏移量,常规文件都可以设置偏移量,而设备
一般是不可以设置偏移量的。如果设备不支持lseek,则lseek返回-1 ,并将errno设置
为ESPIPE 。注意fseek和lseek在返回值上有细微的差别,fseek成功时返回0 失败时返回-1 ,要
返回当前偏移量需调用ftell,而lseek成功时返回当前偏移量失败时返回-1 。
2 man page
NAME
lseek - reposition read/write file offset
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fildes, off_t offset, int whence);
DESCRIPTION
The lseek() function repositions the offset of the open file asso-
ciated with the file descriptor fildes to the argument offset
according to the directive whence as follows:
SEEK_SET
The offset is set to offset bytes.
SEEK_CUR
The offset is set to its current location plus offset bytes.
SEEK_END
The offset is set to the size of the file plus offset bytes.
The lseek() function allows the file offset to be set beyond the
end of the file (but this does not change the size of the file).
If data is later written at this point, subsequent reads of the
data in the gap (a "hole") return null bytes (’\0’) until data is
actually written into the gap.
RETURN VALUE
Upon successful completion, lseek() returns the resulting offset
location as measured in bytes from the beginning of the file. Oth-
erwise, a value of (off_t)-1 is returned and errno is set to indi-
cate the error.
**************************************************************************
1.fcntl()
先前我们以read终端设备为例介绍了非阻塞I/O ,为什么我们不直接对STDIN_FILENO做非阻
塞read,而要重新open一遍/dev/tty 呢?因为STDIN_FILENO在程序启动时已经被自动打开了,而
我们需要在调用open时指定O_NONBLOCK标志。这里介绍另外一种办法,可以用fcntl函数改变一
个已打开的文件的属性,可以重新设置读、写、追加、非阻塞等标志(这些标志称为File Status
Flag ),而不必重新open文件。
2.man page
NAME
fcntl - manipulate file descriptor
SYNOPSIS
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
File status flags
Each open file description has certain associated status flags,
initialized by open(2) and possibly modified by fcntl(2).
The file status flags and their semantics are described in
open(2).
F_GETFL
Read the file status flags.
F_SETFL
Set the file status flags to the value specified by arg.
File access mode (O_RDONLY, O_WRONLY, O_RDWR) and file
creation flags (i.e., O_CREAT, O_EXCL, O_NOCTTY,
O_TRUNC) in arg are ignored. On Linux this command can
only change the O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME,
and O_NONBLOCK flags.
示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
char buf[10];
int n;
long arg;
arg = fcntl(0,F_GETFL);
arg|= O_NONBLOCK;
fcntl(0,F_SETFL,arg);
tryagain:
n = read(0,buf,10);
if(n<0)
{
if(errno == EAGAIN)
goto tryagain;
perror("read");
exit(1);
}
write(1,buf,n);
}
*******************************
获得与进程有关的ID
1. UID(user id) 用户标识号:用于标识正在运行进程的用户。
2. GID(goup id) 用户组标识号:用于标识正在运行的进程的用户所属组的组ID
3. PID(process id)进程标示号:用于标示进程。
4. PGID(process group id) 进程组标示号:用于标示进程所属的进程组ID.一个
进程可以属于某个进程组,可以发信号给某个进程组。注意不同于GID;
SYNOPSIS
#include <unistd.h>
#include <sys/types.h>
1 uid_t getuid(void);
DESCRIPTION
getuid() returns the real user ID of the current process.
2 gid_t getgid(void);
DESCRIPTION
getgid() returns the real group ID of the current process.
3 pid_t getpid(void);
pid_t getppid(void);
DESCRIPTION
getpid() returns the process ID of the current process. (This is often
used by routines that generate unique temporary filenames.)
getppid() returns the process ID of the parent of the current process.
4 getpgrp() always returns the current process group
#include <stdio.h>
//#include <sys/types.h>
#include <unistd.h>
int main(void)
{
printf("Current process's UID = %d\n",getuid());
printf("Current process's GID = %d\n",getgid());
printf("Current process's PID = %d\n",getpid());
printf("Current process's PPID = %d\n",getppid());
printf("Current process's Group ID= %d\n",getpgrp());
return 0;
}
***********************************************************************
1.进程的pcb所包含的信息PCB(Process Control Block )
1 进程id 。系统中每个进程有唯一的id ,在C 语言中用pid_t类型表示,其实就是一个非负整数。
2 进程的状态,有运行、挂起、停止、僵尸等状态。
3 进程切换时需要保存和恢复的一些CPU寄存器。
4 描述虚拟地址空间的信息。
5 描述控制终端的信息。
6 当前工作目录(Current Working Directory)。
7 umask掩码。
8 文件描述符表,包含很多指向file结构体的指针。
9 和信号相关的信息。
10 用户id 和组id 。
11 控制终端、Session和进程组。
12 进程可以使用的资源上限(Resource Limit)。
fork和exec是本章要介绍的两个重要的系统调用。
fork的作用是根据一个现有的进程复制出一个新进程,原来的进程称为父进程(Parent Process ),新进程称为子进程(ChildProcess)。系统中同时运行着很多进程,这些进程都是从最初只有一个进程init
开始一个一个复制出来的。在Shell下输入命令可以运行一个程序,是因为Shell进程在读取用户输入的命令之后会调用fork复制出一个新的Shell进程,然后新的Shell进程调用exec执行新的程序。我们知道一个程序可以多次加载到内存,成为同时运行的多个进程,例如可以同时开多个终端窗口运行/bin/bash,另一方面,一个进程在调用exec前后也可以分别执行两个不同的程序,例如在Shell提示符下输入命令ls,首先fork创建子进程,这时子进程仍在执行/bin/bash程序,然后子进程调用exec执行新的程序/bin/ls,如下
/bin/bash --fork-- /bin/bash/ --exec-- /bin/ls
parent child
2环境变量
exec系统调用执行新程序时会把命令行参数和环境变量表传递给main函数,它们在
整个进程地址空间中的位置如下图所示。
地址:
高 |----------|--
| |命令行参数和环境变量
|----------|--
| |栈
| |
| |
| |
| |
| |
| |
| |堆
|----------|--
| |
| |
|未初始数据|被exec初始化为0
|----------|--
| |
|初始化数据|
|----------|
| |exec从程序文件中读到
| 正文 |
低 |----------|--
libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用
时要用extern 声明。
#include <stdio.h>
int main(void)
{
extern char **environ;
int i;
for(i=0; environ[i]!=NULL; i++)
printf("%s\n", environ[i]);
}
int main(int argc, char* argv[], char *argp[])
由于父进程在调用fork创建子进程时会把自己的环境变量表也复制给子进程,所以a.out打印的
环境变量和Shell进程的环境变量是相同的。
3 进程控制
3.1 fork
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
fork调用失败则返回-1
DESCRIPTION
fork() creates a child process that differs from the parent process
only in its PID and PPID, and in the fact that resource utilizations
are set to 0. File locks and pending signals are not inherited.
Under Linux, fork() is implemented using copy-on-write pages, so the
only penalty that it incurs is the time and memory required to dupli-
cate the parent’s page tables, and to create a unique task structure
for the child.
RETURN VALUE
On success, the PID of the child process is returned in the parent’s
thread of execution, and a 0 is returned in the child’s thread of exe-
cution. On failure, a -1 will be returned in the parent’s context, no
child process will be created, and errno will be set appropriately.
示例分析 子进程
3 #include <stdio.h>
4 #include <stdlib.h>
5 int main()
6 {
7 | pid_t pid;
8 | char * message;
9 | int n;
10 ___| pid = fork();
11 | if(pid == -1)
12 | {
13 | perror("fork failed");
14 | exit(1);
15 | }
16 |___ if(pid == 0)
17 |{
18 | message = "This is child \n";
19 | n = 6;
20 ____|}
21 | else
22 | {
23 | message = "This is parent\n";
24 | n = 3;
25 | }
26 |___ for(; n >0 ;n--)
27 | {
28 | printf(message);
29 | sleep(1);
30 | }
31 |return 0;
32 }
示例分析 父进程
3 #include <stdio.h>
4 #include <stdlib.h>
5 int main()
6 {
7 | pid_t pid;
8 | char * message;
9 | int n;
10 ___| pid = fork();
11 | if(pid == -1)
12 | {
13 | perror("fork failed");
14 | exit(1);
15 | }
16 | if(pid == 0)
| {
18 | message = "This is child \n" ;
19 | n = 6;
20 | }
21 |____else
22 |{
23 | message = "This is parent\n" ;
24 | n = 3;
25 |}
26 |for(; n >0 ;n--)
27 | {
28 | printf(message);
29 | sleep(1);
30 | }
31 |return 0;
32 }
注解:
1 fork调用把父进程的数据复制一份给子进程,但此后二者互
不影响,在这个例子中,fork调用之后父进程和子进程的变量message和n 被赋予不同的
值,互不影响。
2 父进程每打印一条消息就睡眠1 秒,这时内核调度别的进程执行,在1 秒这么长的间隙里
(对于计算机来说1 秒很长了)子进程很有可能被调度到。同样地,子进程每打印一条消
息就睡眠1 秒,在这1 秒期间父进程也很有可能被调度到。所以程序运行的结果基本上是父
子进程交替打印,但这也不是一定的,取决于系统中其它进程的运行情况和内核的调度算
法,如果系统中其它进程非常繁忙则有可能观察到不同的结果。另外,读者也可以
把sleep(1);去掉看程序的运行结果如何
3 fork函数的特点概括起来就是“ 调用一次,返回两次” ,子进程中fork的返回值是0 ,而父进
程中fork的返回值则是子进程的id 。
fork的返回值这样规定是有道理的。fork在子进程中返回0 ,子进程仍可以调用getpid 函数得到
自己的进程id ,也可以调用getppid函数得到父进程的id 。在父进程中用getpid 可以得到自己的
进程id ,然而要想得到子进程的id ,只有将fork的返回值记录下来,别无它法。
3.2 exec family function
1,man 2 execve
SYNOPSIS
#include <unistd.h>
int execve(const char *filename, char *const argv[],char *const envp[]);
argv is an array of argument strings passed to the new program. envp
is an array of strings, conventionally of the form key=value, which are
passed as environment to the new program. Both argv and envp must be
terminated by a null pointer. The argument vector and environment can
be accessed by the called program’s main function, when it is defined
as int main(int argc, char *argv[], char *envp[]).
execve() does not return on success, and the text, data, bss, and stack
of the calling process are overwritten by that of the program loaded.
The program invoked inherits the calling process’s PID, and any open
file descriptors that are not set to close-on-exec. Signals pending on
the calling process are cleared. Any signals set to be caught by the
calling process are reset to their default behaviour. The SIGCHLD sig-
nal (when set to SIG_IGN) may or may not be reset to SIG_DFL.
RETURN VALUE
On success, execve() does not return, on error -1 is returned, and
errno is set appropriately.
2,man 3 exec
fork 函数用于创建一个子进程,该子进程 几乎拷贝了父进程的全部内容。但是这个新创建的进程如何执行呢?这个exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件。并用它取代原调用进程的数据段,代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号以外,其它的全部被新的进程替换掉了。
SYNOPSIS
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
这些函数原型看起来很容易混,但只要掌握了规律就很好记。不带字母p (表示path)的exec函
数第一个参数必须是程序的相对路径或绝对路径,例如"/bin/ls"或"./a.out",而不能是"ls"或"a.out"。
对于带字母p 的函数:
如果参数中包含/ ,则将其视为路径名。
否则视为不带路径的程序名,在PATH环境变量的目录列表中搜索这个程序。
1 带有字母l (表示list )的exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令
行参数的个数是可变的,因此函数原型中有... ,... 中的最后一个可变参数应该是NULL,
起sentinel的作用。
2 对于带有字母v(表示vector )的函数,则应该先构造一个指向各参数的指针
数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL,就
像main函数的argv参数或者环境变量表一样。
3 对于以e (表示environment )结尾的exec函数,可以把一份新的环境变量表传给它,其
他exec函数仍使用当前的环境变量表执行新程序。
DESCRIPTION
The exec() family of functions replaces the current process image with
a new process image. The functions described in this manual page are
front-ends for the function execve(2). (See the manual page for
execve() for detailed information about the replacement of the current
process.)
The initial argument for these functions is the pathname of a file
which is to be executed.
The const char *arg and subsequent ellipses in the execl(), execlp(),
and execle() functions can be thought of as arg0, arg1, ..., argn.
Together they describe a list of one or more pointers to null-termi-
nated strings that represent the argument list available to the exe-
cuted program. The first argument, by convention, should point to the
filename associated with the file being executed. The list of argu-
ments must be terminated by a NULL pointer, and, since these are vari-
adic functions, this pointer must be cast (char *) NULL. variadic function(变参函数)
The execv() and execvp() functions provide an array of pointers to
null-terminated strings that represent the argument list available to
the new program. The first argument, by convention, should point to
the filename associated with the file being executed. The array of
pointers must be terminated by a NULL pointer.
The execle() function also specifies the environment of the executed
process by following the NULL pointer that terminates the list of argu-
ments in the parameter list or the pointer to the argv array with an
additional parameter. This additional parameter is an array of point-
ers to null-terminated strings and must be terminated by a NULL
pointer. The other functions take the environment for the new process
image from the external variable environ in the current process.
Some of these functions have special semantics.
The functions execlp() and execvp() will duplicate the actions of the
shell in searching for an executable file if the specified filename
does not contain a slash (/) character. The search path is the path
specified in the environment by the PATH variable. If this variable
isn’t specified, the default path ‘‘:/bin:/usr/bin’’ is used. In addi-
tion, certain errors are treated specially.
If permission is denied for a file (the attempted execve() returned
EACCES), these functions will continue searching the rest of the search
path. If no other file is found, however, they will return with the
global variable errno set to EACCES.
If the header of a file isn’t recognized (the attempted execve()
returned ENOEXEC), these functions will execute the shell with the path
of the file as its first argument. (If this attempt fails, no further
searching is done.)
示例
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
extern char **environ;
int main()
{
if(fork() == 0)
{
char *argv[] = {"ls","-l",NULL};
char **envp= environ;
//if(execl("/bin/ls","-l",NULL) < 0)
//if(execlp("ls","ls","-l",NULL) < 0)
//if(execle("/bin/env","env",NULL,environ) < 0)
// if(execv("/bin/ls",argv)<0)
// if(execvp("ls",argv)<0)
if(execve("/bin/ls",argv,envp)<0)
perror("execl()");
}
exit(0);
}
补充:
注意事项:
在使用exec函数族的时候,一定要加上错误判断语句,因为exec很容易失败,其中最常见的原因:
1,找不到文件或路径,此时errno被设置为ENOENT;
2,数组argv和arvp,忘了用NULL结束。此时errno被设置为EFAULT;
3,没有对应的可执行权限,此时,errno被调置为EACCES
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
返回值:成功返回新分配的文件描述符,出错返回-1并设置errno
pathname 参数是要打开或创建的文件名,和fopen一样,pathname 既可以是相对路径也可以是绝对路径。
flags参数有一系列常数值可供选择,可以同时选择多个常数用按位或运算符连接起来,
所以这些常数的宏定义都以O_开头,表示or 。
以下三个三先一:
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 可读可写打开
以下可多选(以或的形式)
O_APPEND
表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
O_CREAT
若此文件不存在则创建它。使用此选项时需要提供第三个参数mode,表示该文件的访问权限。
O_EXCL
如果同时指定了O_CREAT,并且文件已存在,则出错返回。
O_TRUNC
如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断(Truncate )为0字节。
O_NONBLOCK
对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O (Nonblock I/O ),非阻塞I/O
函数与C 标准I/O 库的fopen函数有些细微的区别:
1 以可写的方式fopen一个文件时,如果文件不存在会自动创建,而open一个文件时必须明
确指定O_CREAT才会创建文件,否则文件不存在就出错返回。
2 以w 或w+方式fopen一个文件时,如果文件已存在就截断为0 字节,而open一个文件时必须
明确指定O_TRUNC才会截断文件,否则直接在原来的数据上改写。
第三个参数mode指定文件权限,可以用八进制数表示,比如0644表示-rw-r--r--
实验
1.umask
0022
用touch命令创建一个文件时,创建权限是0666,而touch进程继承了Shell进程的umask掩码,
所以最终的文件权限是0666&~022=0644 。
2.touch aaa.c
ll aaa.c
-rw-r--r-- 1 root root 0 01-30 14:18 aaa.c
同样道理,用gcc 编译生成一个可执行文件时,创建权限是0777,而最终的文件权限
是0777&~022=0755 。
gcc aaa.c
ll a.out
-rwxr-xr-x 1 root root 4943 01-30 14:20 a.out
3.umask 0
再重复上述实验
注:
当文件创建时,mode参数提供新建文件的权限。系统并不在该次打开文件时
检查权限,所以你可以进行相反的操作,例如设置文件为只读权限,但却在
打开文件后进行写操作。
==========================================================
Given a pathname for a file, open() returns a file
descriptor, a small, non-negative integer for use in
subsequent system calls (read(2), write(2), lseek(2),
fcntl(2), etc.). The file descriptor returned by a suc-
cessful call will be the lowest-numbered file descriptor
not currently open for the process.
The parameter flags must include one of the following
access modes: O_RDONLY, O_WRONLY, or O_RDWR. These
request opening the file read-only, write-only, or
read/write, respectively.
In addition, zero or more file creation flags and file
status flags can be bitwise-or’d in flags. The file
creation flags are O_CREAT, O_EXCL, O_NOCTTY, and
O_TRUNC. The file status flags are all of the remaining
flags listed below. The distinction between these two
groups of flags is that the file status flags can be
retrieved and (in some cases) modified using fcntl(2).
The full list of file creation flags and file status
flags is as follows:
O_APPEND
The file is opened in append mode. Before each
write(), the file offset is positioned at the end
of the file, as if with lseek(). O_APPEND may
lead to corrupted files on NFS file systems if
more than one process appends data to a file at
once. This is because NFS does not support
appending to a file, so the client kernel has to
simulate it, which can’t be done without a race
condition.
O_CREAT
If the file does not exist it will be created.
The owner (user ID) of the file is set to the
effective user ID of the process. The group own-
ership (group ID) is set either to the effective
group ID of the process or to the group ID of the
parent directory (depending on filesystem type
and mount options, and the mode of the parent
directory, see, e.g., the mount options bsdgroups
and sysvgroups of the ext2 filesystem, as
described in mount(8)).
O_EXCL
When used with O_CREAT, if the file already
exists it is an error and the open() will fail.
In this context, a symbolic link exists, regard-
less of where it points to. O_EXCL is broken on
NFS file systems; programs which rely on it for
performing locking tasks will contain a race con-
dition. The solution for performing atomic file
locking using a lockfile is to create a unique
file on the same file system (e.g., incorporating
hostname and pid), use link(2) to make a link to
the lockfile. If link() returns 0, the lock is
successful. Otherwise, use stat(2) on the unique
file to check if its link count has increased to
2, in which case the lock is also successful.
O_NONBLOCK or O_NDELAY
When possible, the file is opened in non-blocking
mode. Neither the open() nor any subsequent oper-
ations on the file descriptor which is returned
will cause the calling process to wait. For the
handling of FIFOs (named pipes), see also
fifo(7). For a discussion of the effect of
O_NONBLOCK in conjunction with mandatory file
locks and with file leases, see fcntl(2).
O_TRUNC
If the file already exists and is a regular file
and the open mode allows writing (i.e., is O_RDWR
or O_WRONLY) it will be truncated to length 0.
If the file is a FIFO or terminal device file,
the O_TRUNC flag is ignored. Otherwise the effect
of O_TRUNC is unspecified.
The argument mode specifies the permissions to use in
case a new file is created. It is modified by the pro-
cess’s umask in the usual way: the permissions of the
created file are (mode & ~umask). Note that this mode
only applies to future accesses of the newly created
file; the open() call that creates a read-only file may
well return a read/write file descriptor.
RETURN VALUE
open() and creat() return the new file descriptor, or -1
if an error occurred (in which case, errno is set appro-
priately).
ERRORS
EACCES
The requested access to the file is not allowed,
or search permission is denied for one of the
directories in the path prefix of pathname, or
the file did not exist yet and write access to
the parent directory is not allowed. (See also
path_resolution(2).)
EEXIST
pathname already exists and O_CREAT and O_EXCL
were used.
EFAULT
pathname points outside your accessible address space.
creat() is equivalent to open() with flags equal to
O_CREAT|O_WRONLY|O_TRUNC.
creat("aa.c",0666); open("aa.c",O_CREAT|O_WRONLY|O_TRUNC,0666);
2 creat
OWRONLY | OCREAT | OTRUNC 经常被组合使用。
int creat (const char *name, mode_t mode);
fd = open (file, O_WRONLY | O_CREAT | O_TRUNC,0644);
creat(file,0644);
本质:
int creat (const char *name, int mode)
{
return open (name, O_WRONLY | O_CREAT |O_TRUNC, mode);
}
3.close
#include <unistd.h>
int close(int fd);
返回值:成功返回0 ,出错返回-1并设置errno
参数fd是要关闭的文件描述符。需要说明的是,当一个进程终止时,内核对该进程所有尚未关
闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭
它打开的所有文件。但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述
符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。
====================================================================================
close()
closes a file descriptor, so that it no longer
refers to any file and may be reused. Any record locks
(see fcntl(2)) held on the file it was associated with,
and owned by the process, are removed (regardless of the
file descriptor that was used to obtain the lock).
3作业
1.打开文件/home/akae.txt用于写操作,以追加方式打开.
int fd;
fd = open("/home/akae.txt", O_WRONLY | O_APPEND);
if (fd == -1)
{
perror("open");
exit(1);
}
2.打开文件/home/akae.txt用于写操作,如果该文件不存在则创建它,创建权限为0666.
fd = open("/home/akae.txt", O_WRONLY | O_CREAT,0666);
if (fd == -1)
{
perror("open");
exit(1);
}
3.打开文件/home/akae.txt用于写操作,如果该文件已存在则截断为0 字节,如果该文件不
存在则创建它,创建权限为0666.
fd = open("/home/akae.txt", O_WRONLY|O_CREAT|O_TRUNC,0666);
4.打开文件/home/akae.txt用于写操作,如果该文件已存在则报错退出,如果该文件不存在
则创建它,创建权限为0666.
fd = open("/home/akae.txt", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC,0666);
**************************************************************************************************
1 read函数从打开的设备或文件中读取数据
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文
件末尾,则这次read返回0 ssize_t 是signed int类型
参数count是请求读取的字节数,读上来的数据保存在缓冲区buf 中,同时文件的当前读写位置
向后移。注意返回值类型是ssize_t,表示有符号的ssize_t ,这样既可以返回正的字节数、0
(表示到达文件末尾)也可以返回负值-1 (表示出错)。
read函数返回时,返回值说明了buf 中前多少个字节是刚读上来的。
有些情况下,实际读到的字节数(返回值)会小于请求读的字节数count.
例如:
1,读常规文件时,在读到count个字节之前已到达文件末尾。例如,距文件末尾还有30个字
节而请求读100 个字节,则read返回30,下次read将返回0 。
2,从终端设备读,通常以行为单位,读到换行符就返回了。
3,从网络读,根据不同的传输层协议和内核缓存机制,返回值可能小于请求的字节数,后
面socket 编程部分会详细讲
===========================================================================================
DESCRIPTION
read() attempts to read up to count bytes from file
descriptor fd into the buffer starting at buf.
RETURN VALUE
On success, the number of bytes read is returned (zero
indicates end of file), and the file position is
advanced by this number. It is not an error if this
number is smaller than the number of bytes requested;
this may happen for example because fewer bytes are
actually available right now (maybe because we were
close to end-of-file, or because we are reading from a
pipe, or from a terminal), or because read() was inter-
rupted by a signal. On error, -1 is returned, and errno
is set appropriately.
ERRORS
EAGAIN
Non-blocking I/O has been selected using O_NON-BLOCK
and no data was immediately available forreading.
EINTR
The call was interrupted by a signal before any data was read.
2.write函数向打开的设备或文件中写数据
SYNOPSIS
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
返回值:成功返回写入的字节数,出错返回-1并设置errno
写常规文件时,write的返回值通常等于请求写的字节数count,而向终端设备或网络写则不一
定。
读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或
网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网
络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如
果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网
络写则不一定。
====================================================================
DESCRIPTION
write() writes up to count bytes to the file referenced
by the file descriptor fd from the buffer starting at
buf. POSIX requires that a read() which can be proved
to occur after a write() has returned returns the new
data. Note that not all file systems are POSIX conform-
ing.
RETURN VALUE
On success, the number of bytes written are returned
(zero indicates nothing was written). On error, -1 is
returned, and errno is set appropriately. If count is
zero and the file descriptor refers to a regular file, 0
may be returned, or an error could be detected. For a
special file, the results are not portable.
ERRORS
EAGAIN
Non-blocking I/O has been selected using O_NON-
BLOCK and the write would block.
EINTR
The call was interrupted by a signal before any
data was written.
补充:
现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠
(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收
到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运
行(Running )状态,在Linux内核中,处于运行状态的进程分为两种情况:
1. 正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip )里保存着该进程
的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,
正在读写该进程的地址空间。
2. 就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一
个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进
程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进
程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,
同时要兼顾用户体验,不能让和用户交互的进程响应太慢。
*********************************************************************************************************
1
读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或
网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网
络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如
果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网
络写则不一定。
现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠
(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收
到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运
行(Running )状态,在Linux内核中,处于运行状态的进程分为两种情况:
1 正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip )里保存着该进程
的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,
正在读写该进程的地址空间。
2 就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一
个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进
程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进
程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,
同时要兼顾用户体验,不能让和用户交互的进程响应太慢。
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
int main()
{
int n;
char buf[10];
n = read(STDIN_FILENO,buf,10);
if(n<0)
{
perror("stdin_fileno") ;
exit(1);
}
write(STDOUT_FILENO,buf,n);
return 0;
}
解释:
1 Shell进程创建a.out进程,a.out进程开始执行,而Shell进程睡眠等待a.out进程退出。
2 a.out调用read时睡眠等待,直到终端设备输入了换行符才从read返回,read只读走10个
字符,剩下的字符仍然保存在内核的终端设备输入缓冲区中。
3 a.out进程打印并退出,这时Shell进程恢复运行,Shell继续从终端读取用户输入的命令,
于是读走了终端设备输入缓冲区中剩下的字符d 和换行符,把它当成一条命令解释执行,
结果发现执行不了,没有d 这个命令。
2.如何轮询读取多个设备呢?
如果在open一个设备时指定了O_NONBLOCK标志,read/write就不会阻塞。以read为例,如果设备
暂时没有数据可读就返回-1 ,同时置errno为EWOULDBLOCK (或者EAGAIN ,这两个宏定义的值相
同),表示本来应该阻塞在这里(would block,虚拟语气),事实上并没有阻塞而是直接返回
错误,调用者应该试着再读一次(again)。这种行为方式称为轮询(Poll ),调用者只是查询
一下,而不是阻塞在这里死等,这样可以同时监视多个设备:
模型1:
while(1)
{
非阻塞read(设备1);
if(设备1 有数据到达)
处理数据;
非阻塞read(设备2);
if(设备2 有数据到达)
处理数据;
}
非阻塞I/O 有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如
果阻塞在那里,操作系统可以调度别的进程执行,就不会做无用功了。在使用非阻塞I/O 时,通
常不会在一个while循环中一直不停地查询(这称为Tight Loop ),而是每延迟等待一会儿来查
询一下,以免做太多无用功,在延迟等待的时候可以调度其它进程执行
所以----
模型2:
while(1)
{
非阻塞read(设备1);
if(设备1 有数据到达)
处理数据;
非阻塞read(设备2);
if(设备2 有数据到达)
处理数据;
sleep(n);
}
以下是一个非阻塞I/O 的例子。目前我们学过的可能引起阻塞的设备只有终端,所以我们用终端
来做这个实验。程序开始执行时在0 、1 、2 文件描述符上自动打开的文件就是终端,但是没
有O_NONBLOCK标志
读标准输入是阻塞的。我们可以重新
打开一遍设备文件/dev/tty (表示当前终端),在打开时指定O_NONBLOCK标志。
int main()
{
close(0);
int fd;
fd = open("/dev/tty",O_RDONLY|O_NONBLOCK);
printf("fd is %d\n",fd);
char buf[10];
int n;
tryagain:
n = read(fd,buf,10);
// printf("n = %d\n",n);
if(n < 0)
{
if (errno == EAGAIN)
{
sleep(1);
write(2,"try again\n",10);
goto tryagain;
}
perror("read");
exit(1);
}
write(2,buf,n);
close(fd);
}
超时退出示例:
int main()
{
close(STDIN_FILENO);
char buf[10];
int fd;
int n;
fd = open("/dev/tty",O_RDONLY|O_NONBLOCK);
printf("fd=%d\n",fd);
int count = 0;
while(1)
{
n = read(0,buf,10);
//printf("n = %d\n",n);
if(n>=0)
break;
if(n<0)
{
if(errno == EAGAIN)
{
printf("try again\n");
sleep(1);
count++;
if(count == 5)
break;
continue;
}
perror("read");
exit(1);
}
}
if(count == 5)
{
printf("time out!\n");
}
else
{
write(1,buf,n);
}
return 0;
}
3.select
重点讲解
SYNOPSIS
/* According to POSIX.1-2001 */
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
DESCRIPTION
select() allow a program to monitor multiple file
descriptors, waiting until one or more of the file descriptors become
"ready" for some class of I/O operation (e.g., input possible). A file
descriptor is considered ready if it is possible to perform the corre-
sponding I/O operation (e.g., read(2)) without blocking.
Three independent sets of file descriptors are watched. Those listed
in readfds will be watched to see if characters become available for
reading (more precisely, to see if a read will not block; in particu-
lar, a file descriptor is also ready on end-of-file), those in writefds
will be watched to see if a write will not block, and those in
exceptfds will be watched for exceptions. On exit, the sets are modi-
fied in place to indicate which file descriptors actually changed sta-
tus. Each of the three file descriptor sets may be specified as NULL
if no file descriptors are to be watched for the corresponding class of
events.
Four macros are provided to manipulate the sets. FD_ZERO() clears a
set. FD_SET() and FD_CLR() respectively add and remove a given file
descriptor from a set. FD_ISSET() tests to see if a file descriptor is
part of the set; this is useful after select() returns.
nfds is the highest-numbered file descriptor in any of the three sets,
plus 1.
timeout is an upper bound on the amount of time elapsed before select()
returns. It may be zero, causing select() to return immediately. (This
is useful for polling.) If timeout is NULL (no timeout), select() can
block indefinitely.
The timeout
The time structures involved are defined in <sys/time.h> and look like
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
On Linux, select() modifies timeout to reflect the amount of time not
slept; most other implementations do not do this. (POSIX.1-2001 per-
mits either behaviour.) This causes problems both when Linux code
which reads timeout is ported to other operating systems, and when code
is ported to Linux that reuses a struct timeval for multiple select()s
in a loop without reinitializing it. Consider timeout to be undefined
after select() returns.
RETURN VALUE
On success, select() and pselect() return the number of file descrip-
tors contained in the three returned descriptor sets (that is, the
total number of bits that are set in readfds, writefds, exceptfds)
which may be zero if the timeout expires before anything interesting
happens. On error, -1 is returned, and errno is set appropriately; the
sets and timeout become undefined, so do not rely on their contents
after an error.
EXAMPLE
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
fd_set rfds;
struct timeval tv;
int retval;
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don’t rely on the value of tv now! */
if (retval == -1)
perror("select()");
else if (retval)
printf("Data is available now.\n");
/* FD_ISSET(0, &rfds) will be true. */
else
printf("No data within five seconds.\n");
return 0;
}
****************************************************
1 lseek
每个打开的文件都记录着当前读写位置,打开文件时读写位置是0 ,表示文件开头,通常读写多
少个字节就会将读写位置往后移多少个字节。但是有一个例外,如果以O_APPEND 方式打开,每
次写操作都会在文件末尾追加数据,然后将读写位置移到新的文件末尾。lseek和标准I/O 库
的fseek函数类似,可以移动当前读写位置(或者叫偏移量)。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数offset 和whence 的含义和fseek函数完全相同。只不过第一个参数换成了文件描述符。
和fseek一样,偏移量允许超过文件末尾,这种情况下对该文件的下一次写操作将延长文件,中
间空洞的部分读出来都是0 。
若lseek成功执行,则返回新的偏移量,因此可用以下方法确定一个打开文件的当前偏移量:
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);
这种方法也可用来确定文件或设备是否可以设置偏移量,常规文件都可以设置偏移量,而设备
一般是不可以设置偏移量的。如果设备不支持lseek,则lseek返回-1 ,并将errno设置
为ESPIPE 。注意fseek和lseek在返回值上有细微的差别,fseek成功时返回0 失败时返回-1 ,要
返回当前偏移量需调用ftell,而lseek成功时返回当前偏移量失败时返回-1 。
2 man page
NAME
lseek - reposition read/write file offset
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fildes, off_t offset, int whence);
DESCRIPTION
The lseek() function repositions the offset of the open file asso-
ciated with the file descriptor fildes to the argument offset
according to the directive whence as follows:
SEEK_SET
The offset is set to offset bytes.
SEEK_CUR
The offset is set to its current location plus offset bytes.
SEEK_END
The offset is set to the size of the file plus offset bytes.
The lseek() function allows the file offset to be set beyond the
end of the file (but this does not change the size of the file).
If data is later written at this point, subsequent reads of the
data in the gap (a "hole") return null bytes (’\0’) until data is
actually written into the gap.
RETURN VALUE
Upon successful completion, lseek() returns the resulting offset
location as measured in bytes from the beginning of the file. Oth-
erwise, a value of (off_t)-1 is returned and errno is set to indi-
cate the error.
**************************************************************************
1.fcntl()
先前我们以read终端设备为例介绍了非阻塞I/O ,为什么我们不直接对STDIN_FILENO做非阻
塞read,而要重新open一遍/dev/tty 呢?因为STDIN_FILENO在程序启动时已经被自动打开了,而
我们需要在调用open时指定O_NONBLOCK标志。这里介绍另外一种办法,可以用fcntl函数改变一
个已打开的文件的属性,可以重新设置读、写、追加、非阻塞等标志(这些标志称为File Status
Flag ),而不必重新open文件。
2.man page
NAME
fcntl - manipulate file descriptor
SYNOPSIS
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
File status flags
Each open file description has certain associated status flags,
initialized by open(2) and possibly modified by fcntl(2).
The file status flags and their semantics are described in
open(2).
F_GETFL
Read the file status flags.
F_SETFL
Set the file status flags to the value specified by arg.
File access mode (O_RDONLY, O_WRONLY, O_RDWR) and file
creation flags (i.e., O_CREAT, O_EXCL, O_NOCTTY,
O_TRUNC) in arg are ignored. On Linux this command can
only change the O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME,
and O_NONBLOCK flags.
示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
char buf[10];
int n;
long arg;
arg = fcntl(0,F_GETFL);
arg|= O_NONBLOCK;
fcntl(0,F_SETFL,arg);
tryagain:
n = read(0,buf,10);
if(n<0)
{
if(errno == EAGAIN)
goto tryagain;
perror("read");
exit(1);
}
write(1,buf,n);
}
*******************************
获得与进程有关的ID
1. UID(user id) 用户标识号:用于标识正在运行进程的用户。
2. GID(goup id) 用户组标识号:用于标识正在运行的进程的用户所属组的组ID
3. PID(process id)进程标示号:用于标示进程。
4. PGID(process group id) 进程组标示号:用于标示进程所属的进程组ID.一个
进程可以属于某个进程组,可以发信号给某个进程组。注意不同于GID;
SYNOPSIS
#include <unistd.h>
#include <sys/types.h>
1 uid_t getuid(void);
DESCRIPTION
getuid() returns the real user ID of the current process.
2 gid_t getgid(void);
DESCRIPTION
getgid() returns the real group ID of the current process.
3 pid_t getpid(void);
pid_t getppid(void);
DESCRIPTION
getpid() returns the process ID of the current process. (This is often
used by routines that generate unique temporary filenames.)
getppid() returns the process ID of the parent of the current process.
4 getpgrp() always returns the current process group
#include <stdio.h>
//#include <sys/types.h>
#include <unistd.h>
int main(void)
{
printf("Current process's UID = %d\n",getuid());
printf("Current process's GID = %d\n",getgid());
printf("Current process's PID = %d\n",getpid());
printf("Current process's PPID = %d\n",getppid());
printf("Current process's Group ID= %d\n",getpgrp());
return 0;
}
***********************************************************************
1.进程的pcb所包含的信息PCB(Process Control Block )
1 进程id 。系统中每个进程有唯一的id ,在C 语言中用pid_t类型表示,其实就是一个非负整数。
2 进程的状态,有运行、挂起、停止、僵尸等状态。
3 进程切换时需要保存和恢复的一些CPU寄存器。
4 描述虚拟地址空间的信息。
5 描述控制终端的信息。
6 当前工作目录(Current Working Directory)。
7 umask掩码。
8 文件描述符表,包含很多指向file结构体的指针。
9 和信号相关的信息。
10 用户id 和组id 。
11 控制终端、Session和进程组。
12 进程可以使用的资源上限(Resource Limit)。
fork和exec是本章要介绍的两个重要的系统调用。
fork的作用是根据一个现有的进程复制出一个新进程,原来的进程称为父进程(Parent Process ),新进程称为子进程(ChildProcess)。系统中同时运行着很多进程,这些进程都是从最初只有一个进程init
开始一个一个复制出来的。在Shell下输入命令可以运行一个程序,是因为Shell进程在读取用户输入的命令之后会调用fork复制出一个新的Shell进程,然后新的Shell进程调用exec执行新的程序。我们知道一个程序可以多次加载到内存,成为同时运行的多个进程,例如可以同时开多个终端窗口运行/bin/bash,另一方面,一个进程在调用exec前后也可以分别执行两个不同的程序,例如在Shell提示符下输入命令ls,首先fork创建子进程,这时子进程仍在执行/bin/bash程序,然后子进程调用exec执行新的程序/bin/ls,如下
/bin/bash --fork-- /bin/bash/ --exec-- /bin/ls
parent child
2环境变量
exec系统调用执行新程序时会把命令行参数和环境变量表传递给main函数,它们在
整个进程地址空间中的位置如下图所示。
地址:
高 |----------|--
| |命令行参数和环境变量
|----------|--
| |栈
| |
| |
| |
| |
| |
| |
| |堆
|----------|--
| |
| |
|未初始数据|被exec初始化为0
|----------|--
| |
|初始化数据|
|----------|
| |exec从程序文件中读到
| 正文 |
低 |----------|--
libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用
时要用extern 声明。
#include <stdio.h>
int main(void)
{
extern char **environ;
int i;
for(i=0; environ[i]!=NULL; i++)
printf("%s\n", environ[i]);
}
int main(int argc, char* argv[], char *argp[])
由于父进程在调用fork创建子进程时会把自己的环境变量表也复制给子进程,所以a.out打印的
环境变量和Shell进程的环境变量是相同的。
3 进程控制
3.1 fork
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
fork调用失败则返回-1
DESCRIPTION
fork() creates a child process that differs from the parent process
only in its PID and PPID, and in the fact that resource utilizations
are set to 0. File locks and pending signals are not inherited.
Under Linux, fork() is implemented using copy-on-write pages, so the
only penalty that it incurs is the time and memory required to dupli-
cate the parent’s page tables, and to create a unique task structure
for the child.
RETURN VALUE
On success, the PID of the child process is returned in the parent’s
thread of execution, and a 0 is returned in the child’s thread of exe-
cution. On failure, a -1 will be returned in the parent’s context, no
child process will be created, and errno will be set appropriately.
示例分析 子进程
3 #include <stdio.h>
4 #include <stdlib.h>
5 int main()
6 {
7 | pid_t pid;
8 | char * message;
9 | int n;
10 ___| pid = fork();
11 | if(pid == -1)
12 | {
13 | perror("fork failed");
14 | exit(1);
15 | }
16 |___ if(pid == 0)
17 |{
18 | message = "This is child \n";
19 | n = 6;
20 ____|}
21 | else
22 | {
23 | message = "This is parent\n";
24 | n = 3;
25 | }
26 |___ for(; n >0 ;n--)
27 | {
28 | printf(message);
29 | sleep(1);
30 | }
31 |return 0;
32 }
示例分析 父进程
3 #include <stdio.h>
4 #include <stdlib.h>
5 int main()
6 {
7 | pid_t pid;
8 | char * message;
9 | int n;
10 ___| pid = fork();
11 | if(pid == -1)
12 | {
13 | perror("fork failed");
14 | exit(1);
15 | }
16 | if(pid == 0)
| {
18 | message = "This is child \n" ;
19 | n = 6;
20 | }
21 |____else
22 |{
23 | message = "This is parent\n" ;
24 | n = 3;
25 |}
26 |for(; n >0 ;n--)
27 | {
28 | printf(message);
29 | sleep(1);
30 | }
31 |return 0;
32 }
注解:
1 fork调用把父进程的数据复制一份给子进程,但此后二者互
不影响,在这个例子中,fork调用之后父进程和子进程的变量message和n 被赋予不同的
值,互不影响。
2 父进程每打印一条消息就睡眠1 秒,这时内核调度别的进程执行,在1 秒这么长的间隙里
(对于计算机来说1 秒很长了)子进程很有可能被调度到。同样地,子进程每打印一条消
息就睡眠1 秒,在这1 秒期间父进程也很有可能被调度到。所以程序运行的结果基本上是父
子进程交替打印,但这也不是一定的,取决于系统中其它进程的运行情况和内核的调度算
法,如果系统中其它进程非常繁忙则有可能观察到不同的结果。另外,读者也可以
把sleep(1);去掉看程序的运行结果如何
3 fork函数的特点概括起来就是“ 调用一次,返回两次” ,子进程中fork的返回值是0 ,而父进
程中fork的返回值则是子进程的id 。
fork的返回值这样规定是有道理的。fork在子进程中返回0 ,子进程仍可以调用getpid 函数得到
自己的进程id ,也可以调用getppid函数得到父进程的id 。在父进程中用getpid 可以得到自己的
进程id ,然而要想得到子进程的id ,只有将fork的返回值记录下来,别无它法。
3.2 exec family function
1,man 2 execve
SYNOPSIS
#include <unistd.h>
int execve(const char *filename, char *const argv[],char *const envp[]);
argv is an array of argument strings passed to the new program. envp
is an array of strings, conventionally of the form key=value, which are
passed as environment to the new program. Both argv and envp must be
terminated by a null pointer. The argument vector and environment can
be accessed by the called program’s main function, when it is defined
as int main(int argc, char *argv[], char *envp[]).
execve() does not return on success, and the text, data, bss, and stack
of the calling process are overwritten by that of the program loaded.
The program invoked inherits the calling process’s PID, and any open
file descriptors that are not set to close-on-exec. Signals pending on
the calling process are cleared. Any signals set to be caught by the
calling process are reset to their default behaviour. The SIGCHLD sig-
nal (when set to SIG_IGN) may or may not be reset to SIG_DFL.
RETURN VALUE
On success, execve() does not return, on error -1 is returned, and
errno is set appropriately.
2,man 3 exec
fork 函数用于创建一个子进程,该子进程 几乎拷贝了父进程的全部内容。但是这个新创建的进程如何执行呢?这个exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件。并用它取代原调用进程的数据段,代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号以外,其它的全部被新的进程替换掉了。
SYNOPSIS
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
这些函数原型看起来很容易混,但只要掌握了规律就很好记。不带字母p (表示path)的exec函
数第一个参数必须是程序的相对路径或绝对路径,例如"/bin/ls"或"./a.out",而不能是"ls"或"a.out"。
对于带字母p 的函数:
如果参数中包含/ ,则将其视为路径名。
否则视为不带路径的程序名,在PATH环境变量的目录列表中搜索这个程序。
1 带有字母l (表示list )的exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令
行参数的个数是可变的,因此函数原型中有... ,... 中的最后一个可变参数应该是NULL,
起sentinel的作用。
2 对于带有字母v(表示vector )的函数,则应该先构造一个指向各参数的指针
数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL,就
像main函数的argv参数或者环境变量表一样。
3 对于以e (表示environment )结尾的exec函数,可以把一份新的环境变量表传给它,其
他exec函数仍使用当前的环境变量表执行新程序。
DESCRIPTION
The exec() family of functions replaces the current process image with
a new process image. The functions described in this manual page are
front-ends for the function execve(2). (See the manual page for
execve() for detailed information about the replacement of the current
process.)
The initial argument for these functions is the pathname of a file
which is to be executed.
The const char *arg and subsequent ellipses in the execl(), execlp(),
and execle() functions can be thought of as arg0, arg1, ..., argn.
Together they describe a list of one or more pointers to null-termi-
nated strings that represent the argument list available to the exe-
cuted program. The first argument, by convention, should point to the
filename associated with the file being executed. The list of argu-
ments must be terminated by a NULL pointer, and, since these are vari-
adic functions, this pointer must be cast (char *) NULL. variadic function(变参函数)
The execv() and execvp() functions provide an array of pointers to
null-terminated strings that represent the argument list available to
the new program. The first argument, by convention, should point to
the filename associated with the file being executed. The array of
pointers must be terminated by a NULL pointer.
The execle() function also specifies the environment of the executed
process by following the NULL pointer that terminates the list of argu-
ments in the parameter list or the pointer to the argv array with an
additional parameter. This additional parameter is an array of point-
ers to null-terminated strings and must be terminated by a NULL
pointer. The other functions take the environment for the new process
image from the external variable environ in the current process.
Some of these functions have special semantics.
The functions execlp() and execvp() will duplicate the actions of the
shell in searching for an executable file if the specified filename
does not contain a slash (/) character. The search path is the path
specified in the environment by the PATH variable. If this variable
isn’t specified, the default path ‘‘:/bin:/usr/bin’’ is used. In addi-
tion, certain errors are treated specially.
If permission is denied for a file (the attempted execve() returned
EACCES), these functions will continue searching the rest of the search
path. If no other file is found, however, they will return with the
global variable errno set to EACCES.
If the header of a file isn’t recognized (the attempted execve()
returned ENOEXEC), these functions will execute the shell with the path
of the file as its first argument. (If this attempt fails, no further
searching is done.)
示例
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
extern char **environ;
int main()
{
if(fork() == 0)
{
char *argv[] = {"ls","-l",NULL};
char **envp= environ;
//if(execl("/bin/ls","-l",NULL) < 0)
//if(execlp("ls","ls","-l",NULL) < 0)
//if(execle("/bin/env","env",NULL,environ) < 0)
// if(execv("/bin/ls",argv)<0)
// if(execvp("ls",argv)<0)
if(execve("/bin/ls",argv,envp)<0)
perror("execl()");
}
exit(0);
}
补充:
注意事项:
在使用exec函数族的时候,一定要加上错误判断语句,因为exec很容易失败,其中最常见的原因:
1,找不到文件或路径,此时errno被设置为ENOENT;
2,数组argv和arvp,忘了用NULL结束。此时errno被设置为EFAULT;
3,没有对应的可执行权限,此时,errno被调置为EACCES
相关文章推荐
- Linux C语言编程-Linux系统环境--Linux上时间的相关操作---知识点总结
- 自己关于java编程的部分知识点总结
- 黑马程序员--网络通信UDP编程的总结及部分知识点查询
- 3D编程指南第三部分:粒子系统和立即模式渲染(1)
- Android系统小知识点总结
- 库房管理系统--知识点总结
- 网页编程基础第五章知识点总结——框架 表单
- 网络编程知识点总结
- C++知识点部分总结
- 机器学习部分知识点总结
- 虚拟机Linux Redhat 9与目标开发板进行系统编程方面的问题总结(二)
- Java 并发编程知识点学习总结 (2)
- C++知识点总结(四)——面向对象的编程细节总结
- java面试准备---JSF系统学习知识点总结---随时更新
- 3D编程指南第三部分:粒子系统和立即模式渲染(2)
- Windows游戏编程总结(二)粒子系统初步
- Java并发编程-并发编程知识点总结
- 多线程编程部分总结
- 系统编程部分技巧
- UML实战总结——机房收费系统UML第一版部分图展