系统编程部分知识点总结

来源:互联网 发布:张靓颖求婚 知乎 编辑:程序博客网 时间:2024/06/05 02:51

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  

   

 

 

   
   
   
 

 


 

原创粉丝点击