1. 进程标识

  • 进程ID标识符是唯一、可复用的。大多数Unix系统实现延迟复用算法,使得赋予新建进程的ID不同于最近终止所使用的ID
  • ID为0的进程通常是调度进程,也常被称为交换进程。它是内核的一部分,是系统进程。
  • ID为1的进程通常是init进程,在自举过程结束时由内核调用。该进程负责在内核自举后启动一个Unix系统,它决不会终止,是一个普通的用户进程,但以超级用户特权运行。
  • ID为2的进程是页守护进程,负责支持虚拟存储器系统的分页操作。
#include <unistd.h>
pid_t getpid(void);
Returns: process ID of calling process
pid_t getppid(void);
Returns: parent process ID of calling process
uid_t getuid(void);
Returns: real user ID of calling process
uid_t geteuid(void);
Returns: effective user ID of calling process
gid_t getgid(void);
Returns: real group ID of calling process
gid_t getegid(void);
Returns: effective group ID of calling process
  • 注意:这些函数都没有出错返回。

2. 函数fork

  • fork函数被调用一次,返回两次。子进程中返回值是0,父进程中返回值是子进程的pid
  • 子进程是父进程的副本,子进程获得父进程的数据空间、堆和栈的副本。注意,在是子进程拥有的副本。父子进程并不共享这些存储空间部分。父子进程共享正文段。
  • 由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、堆和栈的完全副本。作为替代,使用了写时复制技术。
  • 4种平台都支持的变体:vfork;Linux的变体:clone系统调用,允许调用者控制哪些部分由父子进程共享。
  • fork之后是父进程先执行还是子进程先执行是不确定的
  • 父进程中的所有打开文件描述符都被复制到子进程中,父子进程为每个相同的打开描述符共享一个文件表项,故共享同一文件偏移量。如果父子进程写同一描述符执行的文件,又没有任何形式的同步,那么它们的输出就会混合。
  • 在fork之后处理文件描述符有以下两种常见的情况:
    1. 父进程等待子进程完成。这种情况下,父进程无需对其描述符做任何处理。
    2. 父进程和子进程各自执行不同的程序段。这种情况下,fork之后,父子进程各自它们不需要使用的文件描述符。
  • strlen和sizeof的区别:前者不包括null字节,一次函数调用;后者包括null字节,编译时计算

  • 除了文件描述符之外,父进程的很多其他属性也由子进程继承,包括:

    1. 实际用户ID、实际组ID、有效用户ID、有效组ID
    2. 附属组ID
    3. 进程组ID
    4. 会话ID
    5. 控制终端
    6. SUID和SGID标志(stat结构的st_mode成员)
    7. 当前工作目录
    8. 根目录
    9. 文件模式创建屏蔽字umask
    10. 信号屏蔽和处理
    11. 对任一打开文件描述符的执行时关闭(close-on-exec)标志
    12. 环境
    13. 连接的共享存储段
    14. 存储映像
    15. 资源限制
    16. 是否继承nice值由具体实现自行决定
  • 父进程和子进程之间的区别具体如下:

    1. fork的返回值不同
    2. pid不同
    3. 这两个进程的父进程不同
    4. 子进程的tms_utime、tms_stime、tms_cutime和tms_ustime的值设置为0
    5. 子进程不继承父进程设置的文件锁
    6. 子进程的未处理闹钟被清除
    7. 子进程的未处理信号集设置为空集
  • fork失败的两个主要原因:

    1. 系统中已经有了太多的进程
    2. 该实际用户ID的进程总数超过了系统限制
  • fork有以下两种用法:

    1. 一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段。这在网络服务器中是常见的。
    2. 一个进程要执行一个不同的程序。这对shell是常见的情况。某些系统将fork+exec组合成一个操作spawn

3. 函数vfork

  • vfork函数的调用序列和返回值与fork相同,但两者的语义不同:
    1. vfork函数用于创建一个新进程,而该新进程的目的是exec一个新程序,故不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就不会引用该地址空间。不管在子进程调用exec或exit之前,它在父进程的空间中运行。
    2. 另一个区别是vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。故如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

4. 函数exit

  • 5种正常终止方式:
    1. 从main中执行return,等效于调用exit
    2. 调用exit函数,调用各终止处理程序,关闭标准I/O流,最后调用_exit函数
    3. 调用_exit或_Exit
    4. 进程的最后一个线程在其启动例程执行return语句,该进程以终止状态0返回
    5. 进程的最后一个线程调用pthread_exit,进程终止状态总是0
  • 3种异常终止方式:
    1. 调用abort,它产生SIGABRT信号
    2. 当进程接收到某些信号时,信号可由进程自身(如调用abort函数)、其他进程或内核产生
    3. 最后一个线程对“取消”请求做出响应
  • 不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应的进程关闭所有打开描述符,释放它所使用的存储器等。
  • 注意:“退出状态”(3个exit函数的参数或main的返回值)区别于“终止状态”。在最后调用_exit时,内核将退出状态转换为终止状态。

  • 如果父进程在子进程之前终止,则称子进程为孤儿进程。子进程 ppid变为1,称这些进程由init进程收养。一个init进程收养的进程终止时,init会调用一个wait函数取得其终止状态,防止它成为僵尸进程。

  • 如果子进程在父进程之前终止,内核为每个终止子进程保存了一定量的信息,至少包括pid、该进程的终止状态以及该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储区,关闭其所有打开文件。在Unix术语中,一个已经终止、但其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵尸进程zombie/defunct。

5. 函数wait、waitpid

  • 当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。子进程终止是异步事件。
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
Both return: process ID if OK, (see later), or − on error
  • 调用wait或waitpid的进程可能:
    1. 如果其所有子进程都还在运行,则阻塞
    2. 如果一个子进程终止,正等待其父进程获取其终止状态,则取得该子进程的终止状态立即返回
    3. 如果它没有任何子进程,则立即出错返回
  • 如果进程由于收到SIGCHLD信号而调用wait,我们期望wait会立即返回。
  • wait与waitpid的区别
    1. waitpid有一选项,可使调用者不阻塞
    2. waitpid可以控制它所等待的进程
  • 若statloc不是NULL,则终止进程的终止状态就存放在它所指向的单元内。该整型状态字由实现定义,其中某些位表示退出状态(正常返回),其他位则指示信号编号(异常返回),有一位指示是否产生了core文件。

  • waitpid函数中的pid参数的解释:

    pid == -1,等待任一子进程,等价于wait函数
    pid > 0,等待pid等于该值的子进程
    pid == 0,等待组ID等于调用进程组ID的任一子进程
    pid < 0,等待组ID等于pid绝对值的任一子进程

  • waitpid函数中的options参数:WNOHANG(不阻塞)、WCONTINUED、WUNTRACED

  • 如果一个进程fork一个子进程,但不要它等待子进程终止,也不希望子进程处于僵尸状态直到父进程终止,实现这一要求的诀窍是调用fork两次。

#include "apue.h"
#include <sys/wait.h>
int main(void)
{
pid_t pid;
if ((pid = fork()) < ) {
err_sys("fork error");
} else if (pid == ) { /* first child */
if ((pid = fork()) < )
err_sys("fork error");
else if (pid > )
exit(); /* parent from second fork == first child */
/*
* We’re the second child; our parent becomes init as soon
* as our real parent calls exit() in the statement above.
* Here’s where we’d continue executing, knowing that when
* we’re done, init will reap our status.
*/
sleep();
printf("second child, parent pid = %ld\n", (long)getppid());
exit();
}
if (waitpid(pid, NULL, ) != pid) /* wait for first child */
err_sys("waitpid error");
/*
* We’re the parent (the original process); we continue executing,
* knowing that we’re not the parent of the second child.
*/
exit();
}

6. 函数waitid

#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
Returns: if OK, − on error
  • idtype参数:P_PID、P_PGID、P_ALL
  • options参数:WEXITED、WNOHANG...
  • infop参数是指向siginfo结构的指针

7. 函数wait3、wait4

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
pid_t wait3(int *statloc, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);
Both return: process ID if OK, , or − on error
  • 允许内核返回由终止进程及其所有子进程使用的资源概况,包括用户CPU时间总量、系统CPU时间总量、缺页次数、接收到信号的次数等。 man 2 getrusage

8. 竞争条件

  • 当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则发生了竞争条件。fork函数是竞争条件活跃的滋生地。
#include "apue.h"
TELL_WAIT(); /* set things up for TELL_xxx & WAIT_xxx */
if ((pid = fork()) < ) {
err_sys("fork error");
} else if (pid == ) { /* child */
/* child does whatever is necessary ... */
TELL_PARENT(getppid()); /* tell parent we’re done */
WAIT_PARENT(); /* and wait for parent */
/* and the child continues on its way ... */
exit();
}
/* parent does whatever is necessary ... */
TELL_CHILD(pid); /* tell child we’re done */
WAIT_CHILD(); /* and wait for child */
/* and the parent continues on its way ... */
exit();
  • 5个例程

9. 函数exec

#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */ );
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */ );
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
All seven return: − on error, no return on success
  • l表示列表list,新程序的每个命令行参数都是一个单独的参数,空指针结尾
  • v表示矢量vector,指针数组
  • e代表传递一个指向环境字符串指针数组的指针
  • p代表使用调用进程中的environ变量为新程序复制现有的环境

  • 在执行exec后,pid没有改变。但新程序从调用进程继承了下列属性:

    1. pid和ppid
    2. 实际用户ID和实际组ID
    3. 附属组ID
    4. 进程组ID
    5. 会话ID
    6. 控制终端
    7. 闹钟尚余留的时间
    8. 当前工作目录
    9. 根目录
    10. 文件模式创建屏蔽字umask
    11. 文件锁
    12. 进程信号屏蔽
    13. 未处理信号
    14. 资源限制
    15. nice值
    16. tms_utime、tms_stime、tms_cutime、tms_cstime
    17. 对打开文件的处理:若文件描述符的执行时关闭(close-on-exec,默认通过fcntl设置)标志被设置(默认没有设置),则在执行exec时关闭该描述符;否则仍保持打开。POSIX.1明确要求在exec时关闭打开目录流。

10. 更改用户ID和更改组ID

#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
Both return: if OK, − on error
  • 关于谁能更改ID的若干规则(这里讨论更改用户ID的规则,同样适用于组ID)
  • 实际用户ID ruid、有效用户ID euid、保存的设置用户ID sSUID。假定_POSIX_SAVED_IDS为真
    1. 若进程具有root特权,则setuid函数将ruid、euid和sSUID设置为参数uid的值
    2. 若进程没有root特权,但uid等于ruid或sSUID,则setuid函数只将euid设置为uid
    3. 如果上面两个条件都不满足,则errno设置为EPERM,并返回-1
  • 关于内核所维护的3个用户ID,还有注意以下几点:
    1. 只有root进程可以更改ruid。通常,ruid是在用户登录时,由login程序设置的,而且决不会改变它。
    2. 仅当对程序文件设置了SUID位,exec函数才设置euid。没有设置SUID位,则euid = ruid。
    3. sSUID是由exec复制euid而得到的。

10.1 函数setreuid、setregid

#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);
Both return: if OK, − on error
  • 规则:一个非root用户总能交换ruid和euid,这就允许一个设置了SUID的程序交换成普通用户权限后,可以再次交换会SUID权限

10.2 函数seteuid、setegid

#include <unistd.h>
int seteuid(uid_t uid);
int setegid(gid_t gid);
Both return: if OK, − on error
  • 对于非root用户,可将euid设置为其ruid或sSUID;这与setuid函数一样
  • 对于root用户,可将其euid设置为uid,而ruid、sSUID保持不变

  • 组ID:上面所说的一切都以类似方式适用于各个组ID。附属组ID不受setgid、setregid、setegid函数的影响。

11. 解释器文件

12. 函数system

#include <stdlib.h>
int system(const char *cmdstring);
Returns: (see below)

13. 进程会计

  • accton命令启用会计处理;在Linux中,该文件是/var/account/pacct
typedef u_short comp_t; /* 3-bit base 8 exponent; 13-bit fraction */
struct acct
{
char ac_flag; /* flag (see Figure 8.26) */
char ac_stat; /* termination status (signal & core flag only) */
/* (Solaris only) */
uid_t ac_uid; /* real user ID */
gid_t ac_gid; /* real group ID */
dev_t ac_tty; /* controlling terminal */
time_t ac_btime; /* starting calendar time */
comp_t ac_utime; /* user CPU time */
comp_t ac_stime; /* system CPU time */
comp_t ac_etime; /* elapsed time */
comp_t ac_mem; /* average memory usage */
comp_t ac_io; /* bytes transferred (by read and write) */
/* "blocks" on BSD systems */
comp_t ac_rw; /* blocks read or written */
/* (not present on BSD systems) */
char ac_comm[]; /* command name: [8] for Solaris, */
/* [10] for Mac OS X, [16] for FreeBSD, and */
/* [17] for Linux */
};

14. 用户标识

  • 获取登陆名
#include <unistd.h>
char *getlogin(void);
Returns: pointer to string giving login name if OK, NULL on error
  • 如果调用此函数的进程没有连接到用户登陆时所用的终端,则函数会失败。通常称这些进程为守护进程。

15. 进程调度

  • 进程通过调整nice值选择以更低优先级运行。只有特权进程允许提高调度权限。
  • nice值的范围在0~(2*NZERO)-1之间,NZERO为系统默认的nice值。nice值越小,优先级越高。
#include <unistd.h>
int nice(int incr);
Returns: new nice value − NZERO if OK, − on error
#include <sys/resource.h>
int getpriority(int which, id_t who);
Returns: nice value between −NZERO and NZERO− if OK, − on error
#include <sys/resource.h>
int setpriority(int which, id_t who, int value);
Returns: if OK, − on error

16. 进程时间

#include <sys/times.h>
clock_t times(struct tms *buf );
Returns: elapsed wall clock time in clock ticks if OK, − on error
struct tms {
clock_t tms_utime; /* user CPU time */
clock_t tms_stime; /* system CPU time */
clock_t tms_cutime; /* user CPU time, terminated children */
clock_t tms_cstime; /* system CPU time, terminated children */
};
  • 此结构没有包含墙上时钟时间。返回是是相对值,故需要end-start
  • 由此函数返回的clock_t值都用_SC_CLK_TCK转换成秒
    原创文章,转载请声明出处:http://www.cnblogs.com/DayByDay/p/3948380.html

《Unix环境高级编程》读书笔记 第8章-进程控制的更多相关文章

  1. [置顶] 文件和目录(二)--unix环境高级编程读书笔记

    在linux中,文件的相关信息都记录在stat这个结构体中,文件长度是记录在stat的st_size成员中.对于普通文件,其长度可以为0,目录的长度一般为1024的倍数,这与linux文件系统中blo ...

  2. unix环境高级编程-读书笔记与习题解答-第一篇

    从这周开始逐渐的进入学习状态,每天晚上都会坚持写c程序,并且伴随对这本书的深入,希望能写出更高质量的读书笔记和程序. 本书的第一章,介绍了一些关于unix的基础知识,在这里我不想去讨论linux到底是 ...

  3. unix 环境高级编程-读书笔记与习题解答-第二篇

    第四节 输入与输出 上次的笔记中写到的 open, read, write, lseek 以及close ,都是不带缓存的IO函数,这些函数都使用文件描述符进行工作. 上一篇笔记用到的 read(ST ...

  4. [置顶] 文件io(一)--unix环境高级编程读书笔记

    unix-like(后面以linux为例)系统中的文件操作只需要五个函数就足够了,open.close.read.write以及lseek.这些操作被称为不带缓存的io,这里有必要说一下带缓存和不带缓 ...

  5. unix 环境高级编程 读书笔记与习题解答第四篇

    第一章 第六节 第一小节 这一章没有程序设计和API方面的深入学习,而是注重介绍了unix操作系统中的原始数据类型和系统原型函数,错误处理方面的知识. ____unistd.h____ 该文件包含了u ...

  6. unix进程的环境--unix环境高级编程读书笔记

    http://blog.csdn.net/xiaocainiaoshangxiao/article/category/1800937

  7. unix环境高级编程 读书笔记

    1.上班业余时间把书下载下来,第一章读完了,但是程序只能回家运行啦!Fighting!

  8. Unix环境高级编程学习笔记——fcntl

    写这篇文正主要是为了介绍下fcntl,并将我自己在学习过程中的一些理解写下来,不一定那么官方,也有错误,希望指正,共同进步- fcntl: 一个修改一打开文件的性质的函数.基本的格式是 int fcn ...

  9. 《UNIX环境高级编程》笔记——1.UNIX基础知识

    这一章节侧重一些基本概念和书中用到的一些名词. 一.引言 所有的操作都提供服务,典型的服务包括:执行新程序.打开文件.读写文件.分配存储区以及获得当前时间等. 二.UNIX体系结构 其实linux常见 ...

随机推荐

  1. Java NIO(七)管道

    Java NIO 管道是两个线程之间的单向数据连接.Pipe有一个source通道和sink通道(内部类).数据会被写到sink通道,从source通道读取. 给一张Pipe通道的原理图: 创建管道: ...

  2. (转载)Android项目实战(十七):QQ空间实现(二)—— 分享功能 / 弹出PopupWindow

    Android项目实战(十七):QQ空间实现(二)—— 分享功能 / 弹出PopupWindow   这是一张QQ空间说说详情的截图. 分析: 1.点击右上角三个点的图标,在界面底部弹出一个区域,这个 ...

  3. 使用最新vue_cli搭建的模版

    使用最新vue_cli搭建的模版,包含了常用的插件,router和axiox与测试插件.项目的结构如下: 使用之前请打开 REAMME.md 看看. 已经搭建好的框架的下载地址:https://sha ...

  4. What is the difference between arguments and parameters?

    What is the difference between arguments and parameters? Parameters are defined by the names that ap ...

  5. Hello World Spring MVC

    1, Setup Development Environment 1.1, Java SDK | ~ @ yvan-mac (yvan) | => java -version java vers ...

  6. socket代码(简单)

    SERVER: #!/usr/bin/python # -*- coding: utf-8 -*- import socket import time s = socket.socket(socket ...

  7. JS中浮点数相加问题

    https://blog.csdn.net/u012937029/article/details/61191512 采用IEEE 754 Floating-point的浮点数编码方式来表示浮点数 按I ...

  8. C语言-统计数字、字母、特殊字符

    Action() { //统计字符019aBcd8***,4,4,3 int i,z,t; char *str="019aBcd8***"; fun_Count(str,i,z,t ...

  9. node——underscore的使用

    我在做新闻页面时,需要将之前存好点的data.json里的数据显示在首页上,而首页的每条新闻数据不能直接写定在上面,所以我们要将data里面的数据传递进去.我们需要使用underscore的templ ...

  10. There are multiple modules with names that only differ in casing.

    client?4c0e:153 ./src/components/Paginate.vue There are multiple modules with names that only differ ...