linux进程(2)--进程概念

(ˇˍˇ) 想~

本文来自《UNIX环境高级编程》的读书笔记。

  • 进程的创建
  • 执行程序
  • 进程的终止
  • 进程的属性
  • 进程间的关系
  • 进程的控制关系

0.进程的基本

进程的标识:每个进程都有一个非负整数表示的唯一进程ID。进程ID是唯一的,是可以作为识别进程的唯一标识的。

特殊进程ID

  • ID为0的进程:调度进程,也被称为交换进程(swapper)。该进程是内核的一部分,系统进程。

  • ID为1的进程:init进程,在自举结束之后由内核调用。/sbin/init 这个进程负责启动整个系统,读取一些系统的初始化文件,并将系统引导到一个状态。init进程绝不会终止。他不是系统进程,属于用户进程,超级用户特权运行。

  • ID为2的进程:有些unix在虚拟存储实现中,这个进程为页守护进程。

除了进程ID,进程还有其他的标识,如下:

#include <unistd.h>
pid_t getpid(void);    //返回调用进程的ID
pid_t getppid(void);   //调用进程的父进程ID

pid_t getuid(void);    //调用进程的实际用户id
pid_t geteuid(void);   //调用进程的有效用户id
pid_t getgid(void);    //调用进程的实际用户组id
pid_t getegid(void);   //调用进程的有效组ID

关于uid,euid的差别和作用可以参考下面这篇文章的一个问题的讨论:
探讨php调用外部程序的一些细节

1.fork进程的创建

子进程函数:

#include <unistd.h>
pid_t fork(void);
//返回值:子进程返回0,父进程返回子进程的ID;出错返回-1

fork函数调用一次,返回两次,子进程返回0,父进程返回子进程ID。
这样做的原因:(1)使子进程返回0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid来获取其父进程的id。(2)使父进程返回子进程的id的原因:首先一个父进程可以有多个子进程,然后没有一个函数可以获取一个进程所有子进程的id号。

调用之后的结果:子进程实际上就是父进程的一个副本(子进程获得父进程的数据空间,堆和栈的副本)。子进程和父进程不共享这些存储空间,他们只是共享正文段。

改进
上面可以很清楚的看出,子进程复制了一份父进程的副本,这样会感觉没必要,浪费。这时候需要一门技术:写时复制(copy-on-write)
写时复制:数据只有一份,先不全部都复制给你,你要读可以,随便读。但是当你要写的时候,我就复制一份副本给你。参考:写时复制

几个点

1.fork之后子进程和父进程谁先执行的问题,不确定,这个是看操作系统的调度算法的。
2.fork之后,子进程拥有父进程的数据空间,堆栈。
3.父进程和子进程对文件的共享,文件是只有一份的,但是复制了一份文件描述符的表。如果不同步的话,就会造成子进程和父进程写文件混乱。
对文件描述符的操作的情况:
(1)父进程等待子进程完成。父进程不用对文件描述符做任何处理。当子进程终止之后,文件偏移量会更新。
(2)父进程和子进程执行的程序段不同时。他们的文件描述符互相不影响,父进程和子进程会自关闭他们不需要的文件描述符,这样也不会干扰别人使用文件描述符。

fork用法

1.fork+exec 创建子进程之后执行其他程序。
2.fork之后,子进程去运行其他代码段。

2.vfork

vfork与fork的差别:
(1)vfork用于创建一个新的进程,且新进程的目的就是exec一个新的程序。
(2)vfork与fork一样都创建一个子进程,但是它并不会将父进程的地址空间复制到子进程中,因为vfork的子进程会立即调用exec(或exit),所以不需要存放该地址空间。(vfork的子进程在父进程的空间中运行)
(3)vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。

对于父进程已经结束的进程,它们的父进程都会改变为init进程。init进程收养。
“僵尸进程”:一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占有的资源)的进程称为僵尸进程。

3.exit

exit是标准函数库定义的函数。
_exit()和_Exit()
1.main函数的return语句相当于exit
2.二者的差别在于exit会去刷新输出缓冲,而_exit取决于实现。

异常终止情况:
1.调用abort,产生SIGABRT信号。
2.当进程接受某个信号的时候,信号可由进程本身产生,或者其他的进程和内核产生。
3.最后一个线程对“取消”请求作出响应。

4.wait 和 waitpid

当一个进程正常或者异常终止的时候,内核就向父进程发送SIGCHLD,子进程结束是个异步事件,所以这对父进程是个异步事件。系统默认是忽略这个信号的,但也是可以根据这个信号做一些操作的。

调用wait或者waitpid可能会发生什么:

  • 如果所有子进程都在运行,则阻塞。
  • 如果子进程已经终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
  • 如果它没有任何子进程,则立即出错返回。

如果接受到SIGCHLD信号而调用wait的时候,可能会出现阻塞。

#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *staloc, int options);
//返回值:成功,返回进程ID,失败,返回0或者-1

两个函数区别:

  • 在一个子进程终止之前,wait使其调用者阻塞,而waitpid有一个选项可以使调用者不阻塞。
  • waitpid并不等待在其调用之后的第一个终止的子进程,它有若干个选项,可以控制它所等待的进程。
  • waitpid等待某个特定的进程。

waitpid的pid参数作用解释:

pid==-1  等待任一子进程。此种情况下,waitpidwait等效。
pid>0    等待进程ID与pid相等的子进程。
pid==0   等待组ID等于调用进程组ID的任意子进程
pid<-1   等待组id等于pid绝对值的任一子进程。。

5.waitid

#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options)
//返回值:若成功,返回0;若出错,返回-1

这个函数使用特定的方式来决定等待那个子进程。

idtype和id的关系

常量说明
P_PID等待一特定的进程,id包含要等待子进程的ID
P_PGID等待一特定进程组的任一子进程;id包含要等待子进程的进程组ID
P_ALL等待任一进程,忽略id

option参数按照各标志位或运算。

常量说明
WCONTINUED等待一进程,它以前曾被停止,此后又继续,但其状态尚未报告。
WEXITED等待已经退出的进程
WONHANG如无可用的子进程退出状态,立即返回而非阻塞
WNOWAIT不破坏子进程退出状态,该子进程退出之后可以由后续的wait,waitid或者waitpid调用取得
WSTOPPED等待一进程,它已经停止,但其状态尚未报告。

6.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);
pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);
//若成功,返回进程ID,失败返回-1

查看进程系统资源统计信息。

7.竞争条件

定义:当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,我们认为发生了竞争条件。

在apue.h中有一套TELL和WAIT封装的函数来解决竞争条件。
同步

8.函数exec

exec调用一个新的程序,从其main开始运行,exec只是用磁盘上的一个新程序替换当前进程的正文段,数据段,堆段和栈段。

unix有7中exec函数可以使用。
unix的控制原语:fork创建新进程,exec执行新的程序,exit函数处理终止,和wait函数等待终止。

#include <unistd.h>
int execl(const char *pathname, const char* arg0, ... /*(cahr*)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /*(cahr*)0,char *const envp[]*/)
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ...);
int execvp(const char *filename, char *const argv[])
int fexecve(int fd, char *const argv[], char *const envp[]);

区别:

  • 前面四个函数取路径名作为参数,后两个函数取文件名作为参数,最后一个使用文件描述符作为参数。
  • 第二个区别是:参数表的传递(l表示list,v表示vector)。
  • 最后一个区别是以e结尾的函数可以传递一个指向环境字符串指针数组的指针

注意,在exec前后实际用户ID和实际组ID(real ID)保持不变,而有效ID(effect ID)是否改变取决于所执行的程序文件设置用户ID位和设置组ID位(setuid)。如果新程序的设置用户ID位已经设置,则有效用户ID变成程序文件所有者的ID,否则有效用户ID不变。

七个函数之间的关系如下图:
函数之间的关系

有一个程序可以练习一下:

9.更改用户ID和组ID

在unix系统中,特权的管理以及访问控制主要依靠的是用户ID和组ID来控制的。
在设计应用中我们一般使用的是最小特权模型。只给我们程序完成给定任务所需要的最小特权。

setuid与setgid可以更改实际ID和有效ID

#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
//若成功,返回0;若失败,返回-1

规则:
- 若是进程具有超级用户特权,则setuid函数将实际用户ID,有效用户ID以及保存的设置用户ID设置为uid
- 若进程没有超级用户特权,但是uid等于实际用户ID或者保存的设置用户ID,则setuid只将有效用户ID设置为uid,不更改实际用户ID和保存的设置用户ID。(这点很重要)
- 如果上面的条件都不满足,则errno设置为EPERM,并返回-1

注意:
- 只有超级用户可以改变实际用户ID。实际用户ID是在用户登录时,由login(1)程序设置的,并且绝不会改变它。因为login是一个超级用户进程,当它调用setuid的时候改变三个用户ID
- 仅当程序设置了设置用户ID的时候,exec函数才设置有效用户ID。
- 保存的设置用户ID是由exec复制有效用户ID而得到的,如果设置了文件的设置用户ID,则在exec根据文件的用户ID设置了进程的有效用户ID以后,这个副本才被保存起来。

seteuid与setegid

#include <unistd.h>
int seteuid(uid_t uid);
int setegid(gid_t gid);

10.解释器文件

脚本前面的第一行的意思:
#! pathname [optional-argument]

经常使用的如下:
#!/bin/sh

解释器文件的第一行有长度限制的。

11.函数system

ISO C定义了system函数,POSIX包含了system接口

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

一些返回值:
- 当cmdstring为空指针的时候,则仅当命令处理程序可用时,system返回非0值。
- system在其实现中调用了fork,exec和waitpid,因此,当fork失败的时候,返回-1
- 如果exec失败(表示不能执行shell),则其返回值如同shell执行了exit
- 否则三个函数(fork,exec,eaitpid)都成功,那么system返回值是shell的终止状态。

system的实现:

#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>

//没有信号处理的版本
int system(const char *cmdstring)
{
    pid_t pid;
    int   status;

    if(cmdstring == NULL)
    {
        return -1;
    }

    if((pid = fork()) < 0)
    {
        status = -1;
    }
    else if(pid == 0)
    {
        execl("/bin/sh", "sh", "-c", cmdstring, (char*)0);
        _exit(127);
    }
    else
    {
        while(waitpid(pid, &status, 0) < 0)
        {
            if(errno != EINTR)
            {
                status = -1;
                break;
            }
        }
    }
    return status;
}

12.进程的调度

进程的调度策略和调度优先级是由内核确定的。进程可以通过调整nice值来选择以更低的优先级运行(通过调整nice值降低它对CPU的占有,因此该进程是友好的)。只有特权进程允许提高调度权限。

主要讲述使用nice函数来改变进程的优先级的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值