linux应用程序中的进程
2017-04-14 19:53
148 查看
1. 简介:
进程是指一个具有独立功能的程序在某个数据集合上的一次动态执行过程,它是操作系统进行资源分配和调度的基本单元。简单说一个程序至少有一个进程。内核中的struct task_struct结构体用来记录进程的所有信息。PID唯一的表示一个进程。进程有如下的特点:
动态
占用内存和cpu资源
有生命周期
具有独立IO状态
程序的特点如下:
静态的
占用磁盘空间
linux中进程的类型:
交互进程
守护进程:开机时启动,只有关闭了才会停。
进程的状态包括:
运行态
就绪态
阻塞态(睡眠等待)
在32位系统上,进程的寻址空间是4G,进程空间通常会被划分成:用户空间和内核空间,一般用户空间是低3G,内核空间是高1G,当然这个可以更改。相应的进程模式被分成了两种模式:用户空间模式、内核空间模式。
进程的启动方式有两种:手动启动、调度启动。如果你想定时启动进程,你需要更改/etc/crontab文件中内容
2. 在linux环境的命令行中进程的常用命令介绍
2.1 ps:
功能:报告目前进程的快照
使用:
ps ajx
ps aux
【1】其中标识符的意思:
PPID是父进程
PID唯一标识进程
TTY是中断,如果是 ? 则表示的是守护进程,不受中断控制
STAT是状态,
S睡眠态R运行态
+前台进程
空表示后台进程,受终端控制,关闭终端,进程退出,让程序后天运行的方法是 ./a.out &
Z僵尸进程
ps -e -o pid,ppid,stat,command
就要pid、ppid、stat、command这些的信息
ps的其他的一些选项:
ps [选项]
-e 显示所有进程,环境变量
-f 全格式
-h 不显示标题
-l 长格式
-w 宽输出
a 显示终端上地所有进程,包括其他用户地进程
r 只显示正在运行地进程
x 显示没有控制终端地进程
2.2 top:
功能:动态实现系统资源使用情况
操作:
shift + >
shift + <
q结束
2.3 nice:
功能:按照用户指定的优先级运行程序,值的范围是[-20, 19],-20优先级最高。程序默认的优先级是10,top中的NI值对应的是这个值
使用:
让程序的优先级是-11的运行程序:nice --11 ./a.out
让程序的优先级是11的运行程序:nice-11 ./a.out
2.4 renice:
功能:更改正在运行的程序的优先级
使用:
renice -9 PID ,改变正在运行的PID的优先级为9
2.5 kill:
功能:给进程发送信号
使用:
kill -l,列出所有信号
kill -9,杀死进程
2.6 其他:
bg 将挂起的进程在后台执行fg 将挂起的进程放到前台运行
ctrl + z 挂起一个进程(可以理解为暂停)
3. 进程相关函数:
3.1 fork()
功能:创建一个子进程需要包含的头文件:
#include<sys/types.h> //提供类型pid_t的定义
#include<unistd.h>
函数原型:
pid_t fork(void);
返回值:
>0,表示创建成功,返回值是子进程的PID
0,表示子进程,先返回0还是先返回大于0的pid,这个一般有个100:1的概率(先返回父进程的概率大)
-1,出错
说明:
继承的时候,集成fork之前的变量,现代版的fork应用了著名的“写操作时拷贝”(copy-on-write)技术,当两个进程中变量没有变时,指向相同的地址,当其中一个改变时,在新的空间开辟新的物理空间进行存储。
例子:
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t pid; pid = fork(); if (pid < 0) { perror("fail to fork"); } else if (pid == 0) { puts("is in child!"); while(1); } else { puts("is in parent!"); while(1); } return 0; }
3.2 getpid
功能:获得当前进程的id号原型:pid_t getpid(void);
3.3 getppid
功能:获取当前进程的父进程的id号原型:pid_t getppid(void);
3.4 exec函数簇
提供了一个在进程中执行另一个程序的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代当前进程的数据段、代码段和堆栈段。当前进程除了进程号外,其他内容都被替换掉,也可以是linux下任何可执行的脚本文件。像是加载成员:execl, execlp, execle, execv, execvp, execvpe
具体使用,请查看man手册
3.5 exit
功能:终止进程,查看man手册的方式是 man 3 exit,man 2 _exit头文件:
exit:#include <stdlib.h>
_exit:#include <unistd.h>
函数原型:
void exit(int status);
void _exit(int status);
参数:
status,0表示正常结束,其他表示出现错误,进程非正常结束
注意:
exit 在退出时,刷新标准 IO 的缓冲区
_exit在退出进程时不刷新标准 IO 的缓冲区
4 防止进程不完全退出
4.1 孤儿进程
父进程先退出,子进程执行,这样会导致子进程被系统的1号进程(sbin/init)收养。当子进程退出后,他资源会被init进程回收,再父进程退出的瞬间,他会被init进程收养。【1】子进程也是要退出的,此程序的子进程设计的有点问题,一直while不是一个最优的方案。
【2】对于孤儿进程,当退出的时候,资源的释放本来应该由其创建他的进程来做,而创建他的进程先退出后,将会有init进程来做。
4.2 僵尸进程
当子进程先退出,只留下父进程,则会产生僵尸进程,如果父进程退出,僵尸进程也会消失。然而僵尸进程退出后,资源的释放是不完全的(在进程的列表中保留位置)。僵尸进程出现的根本是子进程退出时给父进程的SIGCHLD信号没有被父进程处理。僵尸进程已经放弃了几乎所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置。僵尸进程不可怕,可怕的是数量,如果大量的产生僵尸进程后,将因为没有可用的进程号而不能产生新的进程。处理僵尸进程的方法:
由于僵尸进程是父在,子不在,那么如果在父进程中等待子进程退出,后让父进程也跟着退出,就能避免僵尸进程的出现了。
头文件:
#include<sys/types.h>
#include<sys/wait.h>
函数原型:
pid_t wait(int *status); -- 等待 任意进程 的 退出,否则 阻塞等待
pid_t waitpid(pid_t pid, int *status, int options); -- 可以配置的等待进程的退出
参数:
①pid,
pid>0 , 回收 进程ID等于pid的 子进程 --常用
pid=-1, 回收 任何一个 子进程,此时,和 wait 作用一样--常用
pid=0 , 回收 其组ID等于调用进程的组ID的任一 子进程
pid<-1, 回收 其组ID等于pid的绝对值的任一 子进程
②status: -- wait 和 waitpid 相同
status用来保存子进程结束时的状态。
③option:
WNOHANG:若指定的子进程没结束,则waitpid不阻塞而立即返回,此时,返回值0
WUNTRACED:为了实现某种操作,有pid指定的任一子进程已被暂停,且其状态自暂停以来还未报告过,则返回其状态。
返回值:
wait:
成功:已回收的子进程的进程号
失败:-1
例子:
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <wait.h> #include <stdlib.h> int main(int argc, const char *argv[]) { pid_t pid; pid = fork(); if (pid < 0) { perror("is error"); } else if (pid == 0) { puts("is in child!"); printf("child PID is %d\n", getpid()); exit(0); } else // 一般情况下是父进程先运行 { pid_t mypid; puts("is in parent!"); printf("parent PID is %d\n", getpid()); mypid = wait(NULL); // 会等待子进程的退出 printf("PID %d is release.\n", mypid); while(1); } return 0; }执行的结果如下:
./build/a.out
is in parent!
is in child!
child PID is 5060
parent PID is 5059
PID 5060 is release.
首先,让父进程忙等待不是一个良好的编程习惯,并且有子进程退出时,给父进程一个SIGCHLD信号,则我们可以在父进程检测SIGCHLD信号,当有这个信号的时候,再去wait/waitpid
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <wait.h> #include <stdlib.h> void fun_chld(int signo) { pid_t mypid; mypid = waitpid(-1, NULL, WNOHANG); printf("PID %d is release.\n", mypid); } int main(int argc, const char *argv[]) { pid_t pid; pid = fork(); if (pid < 0) { perror("is error"); } else if (pid == 0) { puts("is in child!"); printf("child PID is %d\n", getpid()); sleep(2); printf("child exit!\n"); exit(0); } else { signal(SIGCHLD, &fun_chld); puts("is in parent!"); printf("parent PID is %d\n", getpid()); sleep(4); printf("parent exit!\n"); exit(0); } return 0; }./build/a.out
is in parent!
is in child!
child PID is 5246
parent PID is 5245
child exit!
PID 5246 is release.
parent exit!
程序中需要注意的是父进程中的sleep(4)其实并没有睡眠等待4s,当子进程退出,给父进程发送SIGCHLD后,父进程中的sleep会被打断,执行完signal的函数后,将会执行sleep下边的语句。
4.3 守护进程
守护进程(Daemon)是一种运行在后台的一种特殊的进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。由于在linux中,每个系统与用户进行交流的界面是终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端被称为这些进程的控制终端,当控制终端被关闭的时候,相应的进程也会自动关闭。但是守护进程却能突破这种限制,它脱离于终端并且在后台运行,并且它脱离终端的目的是为了避免进程在运行的过程中的信息在任何终端中显示并且进程也不会被任何终端所产生的终端信息所打断。它从被执行的时候开始运转,直到整个系统关闭才退出。如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程。创建守护进程的步骤
1. 创建子进程,父进程退出(先获得孤儿进程)
2. 在子进程中创建新会话
setsid();
创建一个新会话,并且当前进程变为会话组组长。
函数能够使进程完全独立出来,从而脱离所有其他进程的控制。
3. 改变当前目录为根目录
chdir("/"); 通常将守护进程的工作目录设置为根目录。
4. 重设文件权限掩码
umask(0);
5.关闭不需要的文件描述符
int file_num = getdtablesize(); 获得最大的描述符值 + 1;
for(fd = 0; fd < fdtablesize; fd++)
close(fd);
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> int main(int argc, const char *argv[]) { pid_t pid; pid = fork(); if (pid < 0) { perror("fail to fork"); return -1; } else if (pid == 0) { printf("pid = %d\n", getpid()); setsid(); chdir("/"); umask(0); int num = getdtablesize(); int i = 0; for (i = 3; i < num; i++) close(i); int fd; fd = open("file", O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd < 0) { perror("fail to create file"); return -1; } while (1) { sleep(1); write(fd, "helloworld", 10); } } else { exit(0); } return 0; }
一个多进程的例子:
使用父子进程实现文件拷贝。
1. fork产生一个子进程
2. open获得源文件,目标文件描述符
3. 统计源文件字节数
4. 实现为目标文件分配与源文件大小相同的空间
5. 在子进程的代码中先关闭之前继承过来的描述符,然后重新打开两个文件,获得自己的描述符
6. 两个进程在目标文件的不同位置开始拷贝
read write函数实现拷贝
7. 父进程要等待子进程退出,避免僵尸进程
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/wait.h> int main(int argc, const char *argv[]) { int fd_r, fd_w; off_t size; pid_t pid; char a = '\0'; fd_r = open(argv[1], O_RDONLY); if (fd_r < 0) { perror("fail to open fd_r"); return -1; } fd_w = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd_w < 0) { perror("fail to open fd_w"); close(fd_r); return -1; } size = lseek(fd_r, 0, SEEK_END); lseek(fd_w, size - 1, SEEK_SET); if (write(fd_w, &a, 1) <= 0) { perror("fail to write hole..."); close(fd_r); close(fd_w); return -1; } size /= 2; pid = fork(); if (pid < 0) { perror("fail to fork"); close(fd_r); close(fd_w); return -1; } else if (pid == 0) { close(fd_r); close(fd_w); char buf[64] = {0}; ssize_t bytes; fd_r = open(argv[1], O_RDONLY); fd_w = open(argv[2], O_WRONLY); lseek(fd_r, size, SEEK_SET); lseek(fd_w, size, SEEK_SET); while ((bytes = read(fd_r, buf, sizeof(buf)))) { write(fd_w, buf, bytes); } close(fd_r); close(fd_w); exit(0); } else { lseek(fd_r, 0, SEEK_SET); lseek(fd_w, 0, SEEK_SET); char buf[64] = {0}; ssize_t bytes; while (size > 0) { if (size > sizeof(buf)) { bytes = read(fd_r, buf, sizeof(buf)); } else { bytes = read(fd_r, buf, size); } write(fd_w, buf, bytes); size -= bytes; } wait(NULL); close(fd_r); close(fd_w); exit(0); } return 0; }
运行时 ./a.out xxx.c xx2.c
结果是将xxx.c复制到了xx2.c
相关文章推荐
- Linux下应用程序开发:QT的内部进程通信
- 将Win32 C/C++应用程序迁移到Linux-进程、线程和共享内存
- dvm进程,linux进程,应用程序进程是否同一概念
- 【原创】《Linux高级程序设计》杨宗德著 - 进程管理与程序开发 - 信号集与屏蔽信号 分类: Linux --- 应用程序设计 2014-11-08 13:19 53人阅读 评论(0) 收藏
- 【原创】《Linux高级程序设计》杨宗德著 - 进程管理与程序开发 - 安装信号与捕捉信号 分类: Linux --- 应用程序设计 2014-11-08 13:00 49人阅读 评论(0) 收藏
- 将 Win32 C/C++ 应用程序迁移到 POWER 上的 Linux,第 1 部分: 进程、线程和共享内存服务 (转载)
- 【原创】《Linux高级程序设计》杨宗德著 - 进程管理与程序开发 - System V进程间通信之消息队列 分类: Linux --- 应用程序设计 2014-11-11 13:16 71人阅读 评论(0) 收藏
- 将Win32 C/C++应用程序迁移到Linux-进程、线程和共享内存
- 将 Win32 C/C++ 应用程序迁移到 POWER 上的 Linux,第 1 部分: 进程、线程和共享内存服务
- 【原创】《Linux高级程序设计》杨宗德著 - 进程管理与程序开发 - 信号应用实例 分类: Linux --- 应用程序设计 2014-11-09 11:33 66人阅读 评论(0) 收藏
- 将Win32 C/C++应用程序迁移到Linux-进程、线程和共享内存(转)
- linux应用程序开发二,进程控制原理——知识要点
- 嵌入式linux应用程序学习-守护进程的创建
- 将 Win32 C/C++ 应用程序迁移到 POWER 上的 Linux,第 1 部分: 进程、线程和共享内存服务
- linux应用程序设计基础--进程控制编程
- 将 Win32 C/C++ 应用程序迁移到 POWER 上的 Linux,第 1 部分: 进程、线程和共享内存服务 (转)
- Linux应用程序设置进程调度策略
- 【Linux应用程序设计1、2】守护进程概念及实验二则
- 【原创】《Linux高级程序设计》杨宗德著 - 进程管理与程序开发 - 管道 分类: Linux --- 应用程序设计 2014-11-05 11:18 75人阅读 评论(0) 收藏
- 【原创】《Linux高级程序设计》杨宗德著 - 进程管理与程序开发 - System V进程间通信基础 分类: Linux --- 应用程序设计 2014-11-11 13:08 51人阅读 评论(0) 收藏