您的位置:首页 > 编程语言

系统编程部分知识点总结

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: