linux进程

一、进程的含义

进程是运行中的程序,进程是操作系统资源分配的基本单位/最小单位。

  • 进程在内存,程序在硬盘(程序代码在硬盘,运行时转存到内存)
  • 同时运行2个程序时,它们是运行在物理内存的2个互不相关的地址上

例1:

// 001.c
#include <stdio.h>
#include <stdlib.h>

int a;
int main()
{
    int b;
    printf("a is %p, b is %p\n", &a, &b);    // 查看a和b的地址
    while(1)
    {
        sleep(1);
    }
    return 0;
}

说明:

Q1:2个程序运行时,为何a的地址是一样的?

显示的地址都是虚拟地址,而非物理地址。
CPU中的MMU(内存管理单元)会根据不同的进程(进程号),将虚拟地址映射到不同的物理地址上。

Q2:2个程序运行时,为何a的地址一样,而b的地址是不一样的?

a是全局变量,存放在数据段;b是局部变量,存放在栈段。
编译完后,数据段的全局变量的地址是固定好的;栈是运行时才会分配地址的,是动态的,因此每次运行时地址都是不同的。


二、进程终止方式

1、return

2、exit

// return与exit基本是等价的
return 0;
exit(0);
// 程序结束的时候,既可以用return 0; 也可以用exit(0)。
// 二者唯一的区别:
//    return 0; 返回时,会将程序控制权先交给程序(返回给函数)
//    exit(0); 退出时,会将程序控制权直接交给内核

3、_exit

exit与_exit的区别:
1、在不同的头文件中定义的。

// man exit
#include <stdlib.h>

void exit(int status);

// man _exit
#include <unistd.h>

void _exit(int status);

2、exit在退出时,会先做一些清理操作(比如:关闭文件描述符),这样会使输出缓存中的数据被刷新一下;_exit不会做。

如:文件IO中,用到一个行缓存,没有用\0,暂时不会被打印出来的。用return或exit,就会将信息刷到屏幕上/文件中。而用_exit是不会做清理的,在退出时若没有打印出来,就一直不会被打印出来。


三、进程创建

1、fork函数

通过fork函数创建进程。
创建进程也可以叫fork一个进程。

Linux系统中的进程,都是由已有的进程派生出来的,即:父进程创建子进程。除了OS启动时,会创建一个init进程外,其他进程都是通过fork创建的。

vfork:最终也是调用了fork。

fork调用成功后,会派生出子进程;若调用失败,会返回-1。
子进程会继承父进程的几乎所有的数据(包括 数据段、代码段、用户id、主id、创建的文件描述符等),相当于父进程的一个拷贝(整个程序都会拷贝),唯一的区别是pid号不一样。

fork之后,就是2个进程在运行,2个进程是相互独立的。

CPU先调用哪个进程,是不能确定的。

例2:

// 002.c
#include <stdio.h>
#include <unistd.h>

int main()
{
    pid_t pid = 0;
    printf("=====\n");    // =====存在于数据段
    pid = fork();
    if(pid < 0) 
    {
        perror("fork");
        return -1;
    }
    printf("ok!\n");
    return 0;
}
父进程与子进程都打印了ok!

fork之后,会产生2个返回值:若返回值>0,则是父进程;若返回值==0,则是子进程。

例3:

// 003.c
#include <stdio.h>
#include <unistd.h>

int main()
{
    pid_t pid = 0;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}

2、进程ID

进程创建后,就会得到一个唯一标识这个进程的ID(进程号)。

include <sys/types.h>
include <unistd.h>

pid_t getpid(void);    // 得到当前调用函数的进程的id
pid_t getppid(void);    // 得到当前调用函数的进程的父进程的id

例4:

// 004.c
#include <stdio.h>
#include <unistd.h>
int main()
{
    pid_t pid;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent pid is %d\n", getpid(), getppid());
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        printf("parent pid is %d, parent parent pid is %d\n", getpid(), getppid());
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
子进程先退出,退出时父进程的pid可以显示出来

bash fork得到父进程,父进程fork得到子进程。

父进程先退出,child的父进程无从得知,child就变成孤儿进程;孤儿进程会被1号init进程收养

四、进程运行时

1、回收子进程

Q:若不想让子进程变为孤儿进程,该如何做?

(1)使用sleep()函数:等待多长时间

例5:

// 005.c
#include <stdio.h>
#include <unistd.h>
int main()
{
    pid_t pid;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent is %d\n", getpid(), getppid());
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        sleep(1);    // 睡1s,保证子进程先退出,然后再运行父进程
        printf("parent pid is %d, child is %d\n", getpid(), pid);
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
先运行子进程,再运行父进程,保证子进程先结束.png

(2)使用getchar函数:等待键盘输入

例6:

// 006.c
#include <stdio.h>
#include <unistd.h>
int main()
{
    pid_t pid;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent is %d\n", getpid(), getppid());
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        getchar();    // 等待键盘输入
        printf("parent pid is %d, child is %d\n", getpid(), pid);
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
子进程运行结束了,而父进程一直没有运行。此时,子进程将变为僵尸态
ps aux,看到a.out为Z+,即为僵尸态

2、进程状态

进程状态
  • 运行态:进程正在运行或在运行队列中等待
  • 不可中断等待态:进程必须等待某个事件完成,在等待中不可以被信号或者定时器唤醒
  • 可中断等待态:进程等待过程中可以被信号或定时器唤醒
  • 停止状态:进程收到信号,如:SIGSTOPSIGSTPSIGINSIGOUT
  • 僵尸态:子进程先于父进程结束,子进程变为僵尸态(子进程虽然结束了,但是资源仍然占用着),父进程需要对子进程资源进行回收即可解除僵尸态(调用wait函数)

3、wait函数

DESCRIPTION</br>
All of these system calls are used to wait for state changes in a child of the calling process, and obtain information about the child whose state has changed. A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a signal. In the case of a terminated child, performing a wait allows the system to release the resources associated with the child; if a wait is not performed, then the terminated child remains in a "zombie" state.</br>
If a child has already changed state, then these calls return immediately. Otherwise they block until either a child changes state or a signal handler interrupts the call (assuming that system calls are not automatically restarted using the SA_RESTART flag of sigaction(2)). In the remainder of this page, a child whose state has changed and which has not yet been waited upon by one of these system calls is termed waitable.

// man wait:
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);   // 只能等到第一个子进程退出
// 父进程调用wait后,父进程就会阻塞,直到子进程运行结束,wait继续运行,此时父进程就对子进程的资源进行回收

pid_t waitpid(pid_t pid, int *status, int options);   // 可以等待指定的子进程退出

wait() and waitpid()</br>
The wait() system call suspends execution of the calling process until one of its children terminates. The call wait(&status) is equivalent to:</br>
waitpid(-1, &status, 0);</br>
The waitpid() system call suspends execution of the calling process until a child specified by pid argument has changed state. By default, waitpid() waits only for terminated children, but this behavior is modifiable via the options argument.

例7:

// 007.c
#include <stdio.h>
#include <unistd.h>
int main()
{
    pid_t pid;
    pid_t pid2;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent is %d\n", getpid(), getppid());
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        getchar();
        pid2 = wait(0);   // 也可以不用返回值pid2。使用pid2意义不大,因为一旦wait返回,肯定子进程退出了;若wait不返回一直阻塞,说明一直在等待  
        // wait中是一个指针,此处不关心此指针,写一个0
        printf("pid2 is %d\n", pid2);
        printf("parent pid is %d, child is %d\n", getpid(), pid);
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
一直在等待键盘输入,a.out是一个僵尸态
ps aux,看到a.out为Z+,即为僵尸态
wait返回的值就是子进程的进程id

例8:

// 008.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    pid_t pid;
    pid_t pid2;
    int status;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent is %d\n", getpid(), getppid());
        printf("it is child process\n");
    }
    else if(pid > 0)
    {
        getchar();
        pid2 = wait(&status);
        printf("pid2 is %d\n", pid2);
        printf("exit status is %d\n", WEXITSTATUS(status));
        printf("parent pid is %d, child is %d\n", getpid(), pid);
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}
子进程在打印ok!并执行到return 0;时,会把0作为退出码;程序将状态码status通过宏定义的方式解析出来,返回exit status

五、子进程执行新程序

子进程若是拷贝父进程,是没有意义的;子进程必须执行新程序,才有创建的意义。

方法:使用exec函数族。

// man exec:
execl, execlp, execle, execv, execvp, execvpe - execute a file

#include <unistd.h>

extern char **environ;

int execl(const char *path, const char *arg, ...);    
// const char *path:只读的路径(相对路径/绝对路径)。
// const char *arg:参数,是字符串。它是可执行文件的名字。
// ...:可变参数
// 若无参数,写 NULL;不管有没有参数,最后结尾的必须是 NULL (为了告诉函数,参数到此为止)
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[]);
// 将参数写进一个数组中,因此不需要可变参数。结尾也是NULL。
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

例9:

(1)

// address.c
#include <stdio.h>
#include <stdlib.h>

int a;
int main()
{
    int b;
    printf("a is %p, b is %p\n", &a, &b);
    return 0;
}

(2)

// 009.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    pid_t pid;
    pid_t pid2;
    int status;
    printf("=====\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child pid is %d, parent is %d\n", getpid(), getppid());
        printf("it is child process\n");
        execl("./address", "address", NULL);
        printf("after execl\n");
    }
    else if(pid > 0)
    {
        getchar();
        printf("parent pid is %d, child is %d\n", getpid(), pid);
        printf("it is parent process\n");
    }
    printf("ok!\n");
    return 0;
}

结果说明:

1)运行009时,在子进程中,把address程序也运行了;
2)没有打印出after execl字符串,也只打印了一个ok!

当子进程调用execl后,子进程就将代码完全替换成了新程序:

  • 子进程的代码段替换成新程序的代码段
  • 原有的数据段和堆段还有其他数据都废弃掉,全部更新为新程序的数据段和堆段)
  • 唯一保留的是进程ID

并开始执行新程序。

因此,execl后面的代码都不起作用了,因此没有打印出after execl字符串,也只打印了一个ok!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容