linux 进程1
一. 进程的开始和结束
1.1. main函数的调用
a. 编译链接时的引导代码。操作系统下的应用程序其实在main执行前也需要先执行一段引导代码才能去执行main,我们写应用程序时不用考虑引导代码的问题,编译连接时(准确说是连接时)由链接器将编译器中事先准备好的引导代码给连接进去和我们的应用程序一起构成最终的可执行程序。
b. 运行时的加载器。加载器是操作系统中的程序,当我们去执行一个程序时(譬如./a.out,譬如代码中用exec族函数来运行)加载器负责将这个程序加载到内存中去执行这个程序。
c. 程序在编译连接时用链接器,运行时用加载器,这两个东西对程序运行原理非常重要
1.2. 进程如何结束
1.2.1. 在main(main函数由其父进程调用,故返回后进程就over)用return,一般原则是程序正常终止return 0,如果程序异常终止则return -1。
1.2.2. 一般终止进程(程序)应该使用exit或者_exit或者_Exit之一。
1.2.3. atexit注册进程终止处理函数
1.2.4. return、exit和_exit的区别:return和exit效果一样,都是会执行进程终止处理函数,但是用_exit终止进程时并不执行atexit注册的进程终止处理函数。
1.2.5. atexit注册多个进程终止处理函数,先注册的后执行(先进后出,和栈一样)
实例:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void func1(void) { printf("func1\n"); } void func2(void) { printf("func2\n"); } int main(void) { printf("hello world.\n"); // 当进程被正常终止时,系统会自动调用这里注册的func1执行 atexit(func2); atexit(func1); printf("my name is lilei hanmeimei\n"); //return 0; //exit(0); _exit(0); }
二. 进程环境
2.1. 环境变量 export查看
root@ubuntu:/home/yaofeng# export declare -x CLUTTER_IM_MODULE="xim" declare -x COLORTERM="gnome-terminal" declare -x COMPIZ_CONFIG_PROFILE="ubuntu" declare -x DBUS_SESSION_BUS_ADDRESS="unix:abstract=/tmp/dbus-6jtZuUHMzw" declare -x DEFAULTS_PATH="/usr/share/gconf/ubuntu.default.path" declare -x DESKTOP_SESSION="ubuntu" declare -x DISPLAY=":0" declare -x GDMSESSION="ubuntu" declare -x GDM_LANG="en_US" declare -x GNOME_DESKTOP_SESSION_ID="this-is-deprecated" declare -x GNOME_KEYRING_CONTROL="/run/user/1000/keyring-h2Nd3d" declare -x GNOME_KEYRING_PID="2186" declare -x GPG_AGENT_INFO="/run/user/1000/keyring-h2Nd3d/gpg:0:1" declare -x GTK_IM_MODULE="ibus" declare -x GTK_MODULES="overlay-scrollbar:unity-gtk-module" declare -x HOME="/root" declare -x IM_CONFIG_PHASE="1" declare -x INSTANCE="Unity" declare -x JOB="gnome-session" declare -x LANG="en_US.UTF-8" declare -x LANGUAGE="en_US" declare -x LESSCLOSE="/usr/bin/lesspipe %s %s" declare -x LESSOPEN="| /usr/bin/lesspipe %s" declare -x LOGNAME="root" declare -x LS_COLORS="rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:" declare -x MAIL="/var/mail/root" declare -x MANDATORY_PATH="/usr/share/gconf/ubuntu.mandatory.path" declare -x OLDPWD declare -x PATH="/usr/local/arm/arm-2009q3/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games" declare -x PWD="/home/yaofeng" declare -x QT4_IM_MODULE="xim" declare -x QT_IM_MODULE="ibus" declare -x SELINUX_INIT="YES" declare -x SESSIONTYPE="gnome-session" declare -x SHELL="/bin/bash" declare -x SHLVL="2" declare -x SSH_AGENT_LAUNCHER="upstart" declare -x SSH_AGENT_PID="2260" declare -x SSH_AUTH_SOCK="/run/user/1000/keyring-h2Nd3d/ssh" declare -x TERM="xterm" declare -x TEXTDOMAIN="im-config" declare -x TEXTDOMAINDIR="/usr/share/locale/" declare -x UBUNTU_MENUPROXY="1" declare -x UPSTART_EVENTS="started starting" declare -x UPSTART_INSTANCE="" declare -x UPSTART_JOB="unity-settings-daemon" declare -x UPSTART_SESSION="unix:abstract=/com/ubuntu/upstart-session/1000/2188" declare -x USER="root" declare -x VTE_VERSION="3409" declare -x WINDOWID="23070854" declare -x XAUTHORITY="/home/yaofeng/.Xauthority" declare -x XDG_CONFIG_DIRS="/etc/xdg/xdg-ubuntu:/usr/share/upstart/xdg:/etc/xdg" declare -x XDG_CURRENT_DESKTOP="Unity" declare -x XDG_DATA_DIRS="/usr/share/ubuntu:/usr/share/gnome:/usr/local/share/:/usr/share/" declare -x XDG_GREETER_DATA_DIR="/var/lib/lightdm-data/yaofeng" declare -x XDG_RUNTIME_DIR="/run/user/1000" declare -x XDG_SEAT="seat0" declare -x XDG_SEAT_PATH="/org/freedesktop/DisplayManager/Seat0" declare -x XDG_SESSION_ID="c2" declare -x XDG_SESSION_PATH="/org/freedesktop/DisplayManager/Session0" declare -x XDG_VTNR="7" declare -x XMODIFIERS="@im=ibus" root@ubuntu:/home/yaofeng#
2.2. 进程中使用环境变量
a. .每一个进程中都有一份所有环境变量构成的一个表格,也就是说我们当前进程中可以直接使用这些环境变量。进程环境表其实是一个字符串数组,用environ变量指向它。
b. 每个进程中环境变量是独立的,可以独自修改环境变量并不会改变其他进程的环境变量
c. 环境变量使用
#include <stdio.h> int main(void) { extern char **environ; // 声明就能用 int i = 0; while (NULL != environ[i]) { printf("%s\n", environ[i]); i++; } return 0; }
d. 获取指定环境变量函数getenv
三. 进程介绍
3.1. 什么是进程
a. 动态过程而不是静态实物
b. 进程就是程序的一次运行过程,一个静态的可执行程序a.out的一次运行过程(./a.out去运行到结束)就是一个进程。
c. 进程控制块PCB(process control block),内核中专门用来管理一个进程的数据结构。
3.2. 进程PID
a. PID是程序被操作系统加载到内存成为进程后动态分配的资源
b. 获取PID相关API:getpid、getppid、getuid、geteuid、getgid、getegid
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(void) { pid_t p1 = -1, p2 = -1; printf("hello.\n"); p1 = getpid(); printf("pid = %d.\n", p1); p2 = getppid(); printf("parent id = %d.\n", p2); return 0; }
c. 进程查看命令ps
(1)ps -ajx 偏向显示各种有关的ID号
(2)ps -aux 偏向显示进程各种占用资源
root@ubuntu:/home/yaofeng# ps PID TTY TIME CMD 3467 pts/3 00:00:00 su 3468 pts/3 00:00:00 bash 3988 pts/3 00:00:00 ps root@ubuntu:/home/yaofeng# ps -ajx PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 0 1 1 1 ? -1 Ss 0 0:05 /sbin/init 0 2 0 0 ? -1 S 0 0:00 [kthreadd] root@ubuntu:/home/yaofeng# ps -aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.2 4592 2184 ? Ss 00:17 0:05 /sbin/init root 2 0.0 0.0 0 0 ? S 00:17 0:00 [kthreadd] root 3 0.0 0.0 0 0 ? S 00:17 0:03 [ksoftirqd/0] root 4 0.0 0.0 0 0 ? S 00:17 0:00 [kworker/0:0]
3.2. 子进程创建
3.2.1. fork创建子进程
a. 进程的分裂生长模式。如果操作系统需要一个新进程来运行一个程序,那么操作系统会用一个现有的进程来复制生成一个新进程。老进程叫父进程,复制生成的新进程叫子进程
b. fork创建一个新进程。这个系统调用复制当前进程,在进程表中创建一个新的表项,新的表项中的许多属性与当前进程是相同的。新进程几乎与原进程一模一样,执行的代码也完全相同,但新的进程有自己的数据空间,环境和文件描述符
c. fork函数返回值等于0则此时程序在子进程中执行,而返回值大于0则在父进程中执行。
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(void) { pid_t p1 = -1; p1 = fork(); // 返回2次 if (p1 == 0) { // 这里一定是子进程 // 先sleep一下让父进程先运行,先死 sleep(1); printf("子进程, pid = %d.\n", getpid()); printf("hello world.\n"); printf("子进程, 父进程ID = %d.\n", getppid()); } if (p1 > 0) { // 这里一定是父进程 printf("父进程, pid = %d.\n", getpid()); printf("父进程, p1 = %d.\n", p1); } if (p1 < 0) { // 这里一定是fork出错了 } // 在这里所做的操作 //printf("hello world, pid = %d.\n", getpid()); return 0; }
3.3. 父子进程间的藕断丝连
a. 子进程有自己独立的PCB
b. 子进程被内核同等调度
c. 子进程几乎与父进程一模一样,执行的代码也完全相同,但新的进程有自己的数据空间,环境和文件描述符
3.4. 进程的消亡
3.4.1. 进程资源的回收
a. linux系统设计时规定:每一个进程退出时,操作系统会自动回收这个进程涉及到的所有的资源(譬如malloc申请的内容没有free时,当前进程结束时这个内存会被释放,譬如open打开的文件没有close的在程序终止时也会被关闭)。但是操作系统只是回收了这个进程工作时消耗的内存和IO,而并没有回收这个进程本身占用的内存(8KB,主要是task_struct和栈内存)
3.4.2. 僵尸进程
a. 子进程先于父进程结束。子进程结束后父进程此时并不一定立即就能帮子进程“收尸”,在这一段(子进程已经结束且父进程尚未帮其收尸)子进程就被成为僵尸进程。
b. 子进程除task_struct和栈外其余内存空间皆已清理
c. 父进程可以使用wait或waitpid以显式回收子进程的剩余待回收内存资源并且获取子进程退出状态。
d. 父进程也可以不使用wait或者waitpid回收子进程,此时父进程结束时一样会隐式回收子进程的剩余待回收内存资源。(这样设计是为了防止父进程忘记显式调用wait/waitpid来回收子进程从而造成内存泄漏)
3.4.3. 孤儿进程
a. 父进程先于子进程结束,子进程成为一个孤儿进程。
b. linux系统规定:所有的孤儿进程都自动成为一个特殊进程(进程1,也就是init进程)的子进程
3.5. 父进程wait回收子进程函数介绍
3.5.1. wait的工作原理
a. 子进程结束时,系统向其父进程发送SIGCHILD信号
b. 父进程调用wait函数后阻塞
c. 父进程被SIGCHILD信号唤醒然后去回收僵尸子进程
d. 若父进程没有任何子进程则wait返回错误
3.5.2. wait应用
3.5.2.1. wait的参数status。status用来返回子进程结束时的状态,父进程通过wait得到status后就可以知道子进程的一些结束状态信息
a. WIFEXITED宏用来判断子进程是否正常终止(return、exit、_exit退出)
b. WIFSIGNALED宏用来判断子进程是否非正常终止(被信号所终止)
c. WEXITSTATUS宏用来得到正常终止情况下的进程返回值的。
3.5.2.2. wait的返回值pid_t,这个返回值就是本次wait回收的子进程的PID
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <stdlib.h> int main(void) { pid_t pid = -1; pid_t ret = -1; int status = -1; pid = fork(); if (pid > 0) { // 父进程 //sleep(1); printf("parent.\n"); ret = wait(&status); printf("子进程已经被回收,子进程pid = %d.\n", ret); printf("子进程是否正常退出:%d\n", WIFEXITED(status)); printf("子进程是否非正常退出:%d\n", WIFSIGNALED(status)); printf("正常终止的终止值是:%d.\n", WEXITSTATUS(status)); } else if (pid == 0) { // 子进程 printf("child pid = %d.\n", getpid()); return 51; //exit(0); } else { perror("fork"); return -1; } return 0; }
3.5.3. waitpid介绍
3.5.3.1. waitpid和wait差别
a. 基本功能一样,都是用来回收子进程
b. waitpid可以回收指定PID的子进程
c. waitpid可以阻塞式或非阻塞式两种工作模式
3.5.3.2. 使用说明
a. ret = waitpid(-1, &status, 0); -1表示不等待某个特定PID的子进程而是回收任意一个子进程,0表示用默认的方式(阻塞式)来进行等待,返回值ret是本次回收的子进程的PID
b. ret = waitpid(pid, &status, 0); 等待回收PID为pid的这个子进程,如果当前进程并没有一个ID号为pid的子进程,则返回值为-1;如果成功回收了pid这个子进程则返回值为回收的进程的PID
c. ret = waitpid(pid, &status, WNOHANG);这种表示父进程要非阻塞式的回收子进程。此时如果父进程执行waitpid时子进程已经先结束等待回收则waitpid直接回收成功,返回值是回收的子进程的PID;如果父进程waitpid时子进程尚未结束则父进程立刻返回(非阻塞),但是返回值为0(表示回收不成功)。
参考文献《朱老师. linux进程全解》