一、进程标识符

1、每个进程都有非负的整形表示唯一的进程ID

几个典型进程的ID及其功能:

2、除了进程ID,每个进程还有一些其他的标识符。

下列函数返回这些标识符:

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); //返回值:调用进程的进程ID
pid_t getppid(void); //返回值:调用进程的父进程ID
uid_t getuid(void); //返回值:调用进程的实际用户ID
uid_t geteuid(void); //返回值:调用进程的有效用户ID
gid_t getgid(void); //返回值:调用进程的实际组ID
gid_t getegid(void); //返回值:调用进程的有效组ID
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>int main()
{int uid;int euid;pid_t pid;pid_t ppid;pid = fork();if (pid < 0){printf("fork faile!!\n");return 0;}else if (pid == 0){printf("pid: %d, ppid: %d, gid: %d, euid: %d,egid: %d\n",getpid(), getppid(), getuid(), geteuid(), getegid());}else{printf("pid: %d, ppid: %d, gid: %d, euid: %d,egid: %d\n",getpid(), getppid(), getuid(), geteuid(), getegid());}return 0;
}

二、实际用户和有效用户

1、实际用户ID和实际用户组ID:
标识我是谁。也就是登录用户的uid和gid,比如我的Linux以 wzb登录,在Linux运行的所有的命令的实际用户ID都是wzb的uid,实际用户组ID都是wzb的gid(可以用id命令查看)。

2、有效用户ID和有效用户组ID:
进程用来决定我们对资源的访问权限。一般情况下,有效用户ID等于实际用户ID,有效用户组ID等于实际用户组ID。当设置-用户-ID
(SUID)位设置,则有效用户ID等于文件的所有者的uid,而不是实际用户ID;同样,如果设置了设置-用户组-ID(SGID)位,则有效用户组ID等于文件所有者的gid,而不是实际用户组ID。

实际用户ID/实际组ID标识进程究竟是谁(即是进程在系统的唯一标识),有效用户ID/有效组ID/附加组ID决定了进程的访问权限。
suid (chmod u+s file)只能应用在可执行文件上,允许任意用户在执行文件时以文件拥有者的身份执行。
sgid (chmod g+s file)只能应用在可执行文件上,使任意用户在执行可执行文件时,将以拥有组成员的身份执行。

说明:suid 和 sgid 表示在bin在运行时,会具有拥有着的权限,换句话说,只要运行该可执行程序,那么运行者也是有权限对拥有者的所有相关文件(可执行程序会读写)进行操作。

三、进程创建

1、fork函数

#include <unistd.h>
pid_t fork(void);

1>一个现有进程可以调用fork创建一个新进程。
返回值:子进程中返回0,父进程中返回子进程ID,出错返回-1。子进程是父进程的副本。例如:子进程获得父进程数据空间、堆和栈的副本(主要是数据结构的副本)。父子进程不共享这些存储空间部分。父子进程共享正文段。

由于fork之后经常归属exec,所以现在很多实现并不执行一个父进程数据段、栈和堆的完全复制。作为替代,使用了写时拷贝(Copy-On-Write)技术。这些区域由父子进程共享,而且内核将他们的访问权限改变为只读的。如果父子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本。
2>一般来说fork之后父进程和子进程的执行顺序是不确定的,这取决于内核的调度算法。
fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中。父子进程的每个相同的打开描述符共享一个文件表项。假设一个进程有三个不同的打开文件,在从fork返回时,我们有如下所示结构:

3>在fork之后处理的文件描述符有两种常见的情况:
1. 父进程等待子进程完成。在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,子进程对文件偏移量的修改已执行的更新。
2. 父子进程各自执行不同的程序段。这种情况下,在fork之后,父子进程各自关闭他们不需要使用的文件描述符,这样就不会干扰对方使用文件描述符。这种方法在网络服务进程中经常使用。
4>父子进程之间的区别:
1. fork的返回值
2. 进程ID不同
3. 具有不同的父进程ID
4. 子进程的tms_utime、tms_stime、tms_cutime及tms_ustime均被设置为0
5. 父进程设置的文件锁不会被子进程继承
6. 子进程的未处理闹钟被清除
7. 子进程的未处理信号集被设置为空集
5>fork有下面两种用法:
1. 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
2. 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

6>fork调用失败的原因:
1. 系统中有太多的进程
2. 实际用户的进程数超过了限制

2、vfork函数
vfork用于创建一个新进程,而该新进程的目的是exec一个新程序。vfork与fork都创建一个子进程,但它不将父进程的地址空间复制到子进程中,因为子进程会立即调用exec,于是不会存访问该地址空间。相反,在子进程调用exec或exit之前,它在父进程的空间中运行,也就是说会更改父进程的数据段、栈和堆。vfork和fork另一区别在于:vfork保证子进程先运行,在它调用exec或(exit)之后父进程才可能被调度运行。

四、进程等待

1>为什么要进行进程等待?
用来回收子进程状态(如僵尸状态),回收子进程的信息和资源。
进程的退出码:main函数的返回值或exit的参数,进程的退出码用来判断进程运行是否正确。

2>wait和waitpid函数作用
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:
如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。
当一个进程正常或异常终止时,内核就向其父进程发送一个SIGCHLD信号。因为子进程终止是一个异步事件,所以发生这种信号也是内核向父进程发的异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数。对于这种信号的系统默认动作是忽略它。

3>调用wait或waitpid的进程可能会发生什么情况:
1.如果其所有子进程都还在运行,则阻塞
2.如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状 态立即返回。
3.如果它没有任何子进程,则立即出错返回。

4>wait函数
用来等待任何一个子进程退出,由父进程调用。

#include<sys/types.h>
#include<sys/wait.h>
pid_t  wait(int* status)

返回值:成功返回被等待子进程的pid,失败返回-1
status:输出型参数,拿回子进程的退出信息,不关心则可以设置成为NULL
wait:阻塞式调用,等待的子进程不退出时,父进程一直不退出

如果进程由于接收到SIGCHLD而调用wait,则可期望wait会立即返回。但如果在任意时刻调用wait,则进程可能阻塞。
在一个子进程 终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。
如果status不是一个空指针,则终止进程的终止状态就存放在它所指的单元内。如果不关心终止状态,则可将该参数设为空指针(waitpid同样适用)。

5>waitpid函数

#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int* status,int options)

返回值:
1. 当正常返回的时候waitpid返回收集到的子进程的进程ID;
2. 如果设置了选项WNOHANG(非阻塞式调用),而调用中waitpid发现没有已退出的子进程可收集,则返回0;
3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
4. 当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD.

参数:
1. pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
Pid==0等待其组ID等于调用进程组ID的任一个⼦子进程。
Pid<-1等待其组ID等于pid绝对值的任一子进程。

2. status:
WIFEXITED(status) : 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status) : 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
3. options:
WNOHANG :若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。
若正常结束,则返回该子进程的ID。

WIFEXITED(status) : 若为正常终止子进程返回的状态,则为真。
WEXITSTATUS(status) :如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。

6>进程的阻塞式等待代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid;pid = fork();if(pid < 0){printf("%s fork error\n",__FUNCTION__);return 1;}else if( pid == 0 )//child{ printf("child is run, pid is : %d\n",getpid());sleep(5);exit(257);}
else//father
{int status = 0;pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5Sprintf("this is test for wait\n");if( WIFEXITED(status) && ret == pid ){printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));}else{printf("wait child failed, return.\n");return 1;}
}
return 0;
}

进程的非阻塞等待方式代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid;pid = fork();if(pid < 0){printf("%s fork error\n",__FUNCTION__);return 1;}else if( pid == 0 ){ //childprintf("child is run, pid is : %d\n",getpid());sleep(5);exit(1);}else{int status = 0;pid_t ret = 0;do{ret = waitpid(-1, &status, WNOHANG);//⾮非阻塞式等待if( ret == 0 ){printf("child is running\n");}sleep(1);}while(ret == 0);if( WIFEXITED(status) && ret == pid ){printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));}else{printf("wait child failed, return.\n");return 1;}
}
return 0;
}

五、进程的程序替换

1、用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
2、有六种以exec开头的函数,统称exec函数:

#include <unistd.h>
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[]);
int execve(const char *path, char *const argv[], char *const envp[]);//系统调用,上面5个是对它的封装

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则 返回-1, 所以exec函数只有出错的返回值而没有成功的返回值。

记忆规律:
不带字母p(表示path)的exec函数 第一个参数必须是程序的相对路径或绝对路径,例如”/bin/ls”或”./a.out”,而不能 是”ls”或”a.out”。
对于带字母p的函数: 如果参数中包含/,则
将其视为路径名。 否则视为不带路径的程序名,在PATH环境变量的目录列表中搜索这个程序。
带有字母l(表示list)的exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,因此函数原型中有…,…中的最后一个可变参数应该是NULL, 起sentinel的作用。
带有字母v(表示vector)的函数,则应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL,就像main函数的argv参数或者环境变量表一样。
对于以e(表示environment)结尾的exec函数,可以把一份新的环境变量表传给它,其他exec函数仍使用当前的环境变量表执行新程序。
3、一个完整的例子

实例:模拟一个 Shell 外壳程序,并且让它支持输出重定向。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/fcntl.h>
#include<sys/wait.h>int main()
{while (1){printf("[test@192.168.110.142 test]$ ");fflush(stdout);char buf[1024];ssize_t s = read(0, buf, sizeof(buf)-1);if (s > 0){buf[s-1]= 0;}char *_myshell[32];_myshell[0] = buf;char* start = buf;int i = 1;while (*start){if (*start == ' '){*start = 0;start++;_myshell[i++] = start;}start++;}_myshell[i] = NULL;if (strcmp(_myshell[0], "exit") == 0){break;}if (strcmp(_myshell[i-2], ">") == 0){_myshell[i - 2] = NULL;pid_t id = fork();if (id < 0){perror("fork error!");}else if (id == 0)//child{close(1);open(_myshell[i-1], O_WRONLY|O_CREAT, 0666);execvp(_myshell[0], _myshell);}else{wait(0);}}else{pid_t id = vfork();if (id < 0){perror("vfork");}else if (0 == id){execvp(_myshell[0], _myshell);}else{wait(0);}}}return 0;
}

六、进程终止

1、进程终止的5种方式

正常退出
从main函数返回–语言级别的返回操作
调用exit–C库函数
调用_exit–系统调用
异常退出
调用abort 产生SIGABOUT信号
由信号终止 ctrl+c /SIGINT

2、exit函数
对于三个终止函数(exit、_exit和_Exit),实现这一点的方法是,将其退出状态作为参数传送给函数。在异常终止情况下,内核产生一个指示其异常终止原因的终止状态。在任意一种情况下,该终止状态的父进程都能使用wait或waitpid函数取得其终止状态。
在调用_exit时,内核将进程的退出状态转换成终止状态。

exit和_exit的区别:
1)_exit是一个系统调用,exit是一个c库函数
2)exit会执行刷新I/O缓存
3)exit会执行调用终止处理程序

Linux下的进程概论与编程二(进程控制)相关推荐

  1. Linux下使用WPS做office的二次开发

    Linux下使用WPS做office的二次开发 序 上个版本WPS在Linux上就已经支持二次开发了,可以直接去看官网相关的介绍.https://open.wps.cn/ 我们选择WPS的客户端进行二 ...

  2. linux下C语言socket网络编程简例

    转自博文:http://blog.csdn.net/kikilizhm/article/details/7858405 在练习写网络编程时,该例给了我帮助,在写服务器时,我把while逻辑位置想法错了 ...

  3. Linux下穿透内网跨平台远程控制与被控制

    Linux下穿透内网跨平台远程控制与被控制 A) 从别的系统控制Linux. 1. 64位 图形界面 2. 64位 命令行界面 3. 32位 图形界面 4. 32位 命令行界面 B) 从Linux控制 ...

  4. Linux运行cat进程,linux下如何使用某个用户启动某个进程?

    安全里有个原则,叫最小权限原则python 根据这个原则,对于启动某个应用或者进程,应该赋予其最小权限,根据应用权限要求,建立一个相应权限的用户,赋予其应用相应的权限,而后使用这个用户启用这个应用li ...

  5. Linux下的文件I/O编程

    2019独角兽企业重金招聘Python工程师标准>>> linux下C语言对于文件的操作,我们会经常用到fopen(),fclose(),fwrite(),fread(),fgets ...

  6. Linux下c语言的图形编程

    简介 GTK+(GIMP TOOLKIT),是一个跨平台的图形界面(GUI)开发工具,是目前LINUX操作系统中较常用的图形界面开发工具之一,它采用一种非常有特色的面向对象的C语言开发框架(C Fra ...

  7. 以下是UNIX linux 下c语言的图形编程  curses库

    UNIX下c语言的图形编程--curses.h 函式库 作者:不详  来源:supcode.com收集整理  发布时间:2005-7-22 19:39:36 减小字体 增大字体 相信您在网路上一定用过 ...

  8. Linux下c语言的图形编程(转) curses.h 函式库

    这是转贴哈,最近想吧TC的图形编程搞到LINUX下面来,所以就对其有所研究了哦. http://tech.techweb.com.cn/viewthread.php?tid=181892 注明了转载, ...

  9. 查看端口所在进程linux,linux下用lsof查看端口所在的进程

    广告一下,我专门给大家翻译外文IT参考资料,想学的,可以关注,也可以关注我个人自己建的博客:http://www.1024sky.cn lsof查看端口被谁占用 2010-05-27 15:38 ls ...

最新文章

  1. Ubuntu10.04安装Flash插件
  2. 小米立Flag:要做年轻人的第一个深度学习框架
  3. 数据库的字段是date java里面能用timestamp吗_数据库中DATETIME,DATE和TIMESTAMP类型 的区别...
  4. java 复合_Java复合语句的使用方法详解
  5. 简述python的特性_python的一些语言特性(一)
  6. android读写文件
  7. KnowIME: A System to Construct a Knowledge Graph for Intelligent Manufacturing Equipment-学习笔记
  8. cloud一分钟 | 腾讯金融云总经理胡利明:腾讯云服务金融的“加减 乘除”法。...
  9. 43 WM配置-作业-库存盘点-定义每种存储类型的类型
  10. java多线程采集+线程同步-【多线程数据采集之四】
  11. CCPC2020太原理工获得一枚奖牌
  12. LeetCode 72 编辑距离
  13. java的connect和http_【JAVA】通过URLConnection/HttpURLConnection发送HTTP请求的方法
  14. scanner python_Python之Scanner编写
  15. DL加速器与GPU的不同,一个用于推理,一个用于训练。
  16. crt是什么意思 windows编程_从零开始,学习windows编程 - hello.c的疑惑!
  17. MCE公司:DDR1 和 DDR2 双靶点抑制剂的设计合成及其抗炎作用研究
  18. 微信小程序--订单查询页面
  19. 算术左移,算术右移;逻辑左移,逻辑右移
  20. Apture 电容麦克风接口设置

热门文章

  1. 如何基于Spark进行用户画像?
  2. CentOS6.8 mediawiki安装
  3. 10 个非常有用的 AngularJS 框架
  4. 监听列表ListVIew的滑动状态
  5. 程序员应该遵守的编程原则
  6. Excel Oledb设置
  7. Office Communication Server(OCS) 2007存档和CDR的部署
  8. 如何更改OST、OAB文件的默认路径?
  9. CodeForces - 856B Similar Words(AC自动机+树形dp)
  10. HDU多校6 - 6836 Expectation(矩阵树定理+高斯消元求行列式)