2019年8月8日星期四(系统编程)

201988日星期四

. linux系统编程学习大纲

1. 进程的概念,进程诞生与死亡,进程函数接口,进程的意义。

2. 进程之间通信方式:有名管道,无名管道,信号,消息队列,共享内存,信号量

3. linux进程的信号集,设置信号的阻塞状态。

4. 线程的概念,线程与进程的区别?线程诞生与死亡,函数接口。

5. 线程的同步互斥方式,有名信号量,无名信号量,互斥锁,读写锁,条件变量

6. 线程池 -> 为了同时处理多个任务。

. 进程的概念?

1. 什么是程序?什么是进程?

程序就是一堆待执行的代码。  -> 静态的文本数据。 例如: project.c(C语言程序) /  project(可执行程序)。

进程就是当程序被CPU加载,根据每一行代码做出相应的效果,才能形成一个动态的过程,这个过程就称之为进程。

2. linux下,如何开启一个新的进程?

直接在linux下执行程序即可。

例如: ./project   -> 开启一个新的进程!

3. 当进程开启,系统会为进程分配什么资源?

1)会分配进程对应内存空间。

2)任务结构体 -> struct task_struct   -> linux下,任何进程就像一个任务。

结构体在哪里?

Ubuntu: /usr/src/linux-headers-3.5.0-23/include/linux/sched.h

struct task_struct {

       volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */  -> 进程运行状态。

       void *stack;

       ....

       struct uprobe_task *utask;

       int uprobe_srcu_id;

#endif

};

. 关于查看进程信息的linux命令。

1)查看整个linux系统的ID  -> ps -ef(静态)

gec@ubuntu:~$ ps -ef

用户名     PID   PPID    创建时间          持续时间   进程名字   

root         1     0  0 16:37    ?        00:00:00  /sbin/init          -> 祖先进程

gec       2272     1  0 16:37    ?        00:00:01  gnome-terminal      -> linux终端

gec       2278  2272  0 16:37    pts/1    00:00:00  bash         -> linux终端的子进程,叫bash进程

gec       2720  2278  0 18:54    pts/1    00:00:00  ps -ef              -> bash进程的子进程,shell命令

2)查看进程CPU使用率  -> top(动态)

gec@ubuntu:~$ top

Tasks: 150 total  -> 当前系统有150个进程  

       2 running     -> 2个在运行态

       148 sleeping  -> 148个睡眠态

       0 stopped     -> 0个暂停态

       0 zombie      -> 0个僵尸态

 PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND 

1049 root      20   0 96160  26m 6700 S  5.3  2.6   0:18.50 Xorg  

S  -> state当前进程状态

%CPU -> 当前瞬间CPU占用率

%MEM -> 当前瞬间内存占用率

3)查看整个系统的关系图  -> pstree

gec@ubuntu:~$ pstree

init─┬─NetworkManager───{NetworkManager}

      ├─accounts-daemon───{accounts-daemon}

      ├─acpid

      ├─gnome-terminal─┬─bash───pstree

. 进程的诞生与死亡。

1.进程有什么状态?

进程生老病死就是从进程开启到进程结束经历所有状态。

就绪态:不占用CPU资源,不运行代码。

运行态:占用CPU资源,运行代码。

暂停态:占用CPU资源,不运行代码,可以切换到就绪态/运行态

睡眠态:占用CPU资源,运行代码,可以切换到就绪态/运行态

僵尸态:占用CPU资源,不运行代码,不可以切换到就绪态/运行态

死亡态:不占用CPU资源,不运行代码。

2. 什么是占用CPU资源?

就是进程自身的资源(任务结构体)没有释放。

3. 什么是僵尸态?

进程结束时,一定会切换到僵尸态。所谓僵尸态,就是本进程已经结束,但是自身的资源还没有释放掉。

详细见:"进程的生老病死.jpg"

4. 需要注意的地方:

1)进程在暂停态时,如果收到继续的信号,是切换到就绪态,而不是运行态。

2)进程退出时,一定会变成僵尸态。

3)进程不可以没有父进程,也不能同时拥有两个父进程。

4)孤儿进程特点:当自己的父进程退出后,会马上寻找继父,而不是等到变成僵尸再找。

5init祖先进程特点:一定会帮所有的孤儿回收资源。

. 进程的函数接口?

单进程程序  -> 只能一行一行代码去执行。

创建子进程意义: 同时处理多个任务。

1. 在进程内部创建一个新的子进程?  -> fork()  -> man 2 fork

功能: fork - create a child process  -> 创建子进程

使用格式:

      #include <unistd.h>

      pid_t fork(void);

   参数:无

   返回值:  pid_t  -> 进程PID号数据类型  %d

       成功: 

              父进程 -> 子进程的PID号  >0

              子进程 -> 0

       失败:

              父进程 -> -1

              没有创建出子进程。

  例题: 在进程内部创建一个新的子进程,看看会不会同时处理两件事情。

#include <unistd.h>

#include <stdio.h>

 

int main(int argc,char *argv[])

{

       /* 现在只有一个进程,就是父进程 */

       printf("hello ");

       printf("world ");

       fork();

       /* 到这里为止,就有两个进程 */

       printf("appletree ");

       return 0;

}

结果1  父进程先运行,子进程后执行。

gec@ubuntu:/mnt/hgfs/GZ1934/09 系统编程/01/code$ ./fork_test

hello

world

appletree  -> 父进程打印

gec@ubuntu:/mnt/hgfs/GZ1934/09 系统编程/01/code$ appletree   -> 子进程打印

结果2  子进程先运行,父进程后执行。

gec@ubuntu:/mnt/hgfs/GZ1934/09 系统编程/01/code$ ./fork_test

hello

world

appletree  -> 子进程打印

appletree  -> 父进程打印

gec@ubuntu:/mnt/hgfs/GZ1934/09 系统编程/01/code$

结论:

1)父子进程谁先运行,是随机的。
2fork()后的代码,两个进程都会执行。

  练习1: 写一个程序,使得子进程先打印apple,父进程再打印hello。

#include <unistd.h>

#include <stdio.h>

int main()

{

       /* 父进程 */

       pid_t x;

       x = fork();

                 /* 父进程 */     /* 子进程 */

//返回值情况:       x > 0          x = 0

       if(x > 0)  //父进程

       {

              usleep(5000);

              printf("hello! ");

       }

       if(x == 0) //子进程

       {

              printf("apple! ");

       }

       return 0;

}

2. 查看自身的ID号以及查看父进程的ID

     getpid()       getppid()   -> man 2 getpid

功能: getpid, getppid - get process identification  -> 获取进程的PID

使用格式:

       #include <sys/types.h>

       #include <unistd.h>

       pid_t getpid(void);

       pid_t getppid(void);

       参数:无

       返回值:

          getpid()

              成功: 当前进程的ID号

              失败: 不存在的!

          getppid()

              成功: 当前进程的PID号

              失败: 不存在的!

   练习2: 在子进程中打印自己与父进程的ID号,在父进程中打印自己与子进程的ID,通过ps -ef命令查看ID是否一致!

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

int main()

{

       pid_t x;

       x = fork();

       if(x > 0) //父

       {

              usleep(10000);

              printf("parent pid = %d ",getpid());

              printf("child pid = %d ",x);

       }

       if(x == 0) //子

       {

              printf("child pid = %d ",getpid());

              printf("parent pid = %d ",getppid());

       }

       return 0;

}

   练习3: 验证孤儿进程会马上寻找继父,而不是等到孤儿进程结束才找。

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

int main(int argc,char *argv[])

{

       pid_t x;

       x = fork();

      

       if(x > 0)

       {

              sleep(2);

       }

       if(x == 0)

       {

              printf("parent pid = %d ",getppid());

              sleep(3);

              printf("parent pid = %d ",getppid());

              printf("helloworld! ");

              sleep(1);

              printf("appletree! ");

       }

       return 0;

}

. 如何解决僵尸问题?

1. 父进程主动回收子进程的资源。  -> wait()   -> man 2 wait

功能: wait for process to change state

使用格式:

       #include <sys/types.h>

       #include <sys/wait.h>

       pid_t wait(int *status);

       status:储存子进程退出状态的指针。

              填NULL,代表父进程不关心子进程的退出状态。

              不填NULL,代表父进程想知道子进程的退出状态。

      返回值:

              成功: 退出的子进程的ID号

              失败: -1

  练习4: 验证wait()可以帮子进程回收资源。

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

#include <sys/wait.h>

int main()

{

       pid_t x;

       x = fork();

       if(x > 0)

       {

              sleep(10);  //有1个僵尸

              wait(NULL);  //僵尸态 -> 死亡态

             

              sleep(8);  //有0个僵尸

       }

       if(x == 0)

       {

              printf("hello! ");  //运行态 -> 僵尸态

       }

       return 0;

}

2. 父进程还在,但是不主动调用wait()去回收资源。

举例子。

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

int main()

{

       pid_t x;

       x = fork();

      

       if(x == 0)

       {

              printf("parent pid = %d ",getppid());

              printf("child helloworld! ");

              //printf("parent pid = %d ",getppid());

       } //子进程: 运行态 -> 僵尸态

       if(x > 0)

       {

              sleep(15); //1个僵尸态

              printf("parent helloworld! ");

              sleep(3);

              printf("parent exit! ");  //子进程就会寻找继父,帮自己回收资源。

       }

       return 0;

}

3. 父进程比子进程先退出,子进程就会马上寻找继父,等待自身变成僵尸态时,就会让继父帮自己收尸!

举例子。

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

int main(int argc,char *argv[])

{

       pid_t x;

       x = fork();

       if(x > 0)

       {

              sleep(2);

       }

       if(x == 0)

       {

              printf("parent pid = %d ",getppid());

              sleep(3);

              printf("parent pid = %d ",getppid());

              printf("helloworld! ");

              sleep(10);   -> 在这10秒内,子进程继父就是祖先进程。

              printf("appletree! ");

                     -> 子进程结束,让继父帮子进程回收资源。

       }

       return 0;

}

. 进程的退出 

    exit()   -> 查询: man 3 exit

exit()函数特点:清洗缓冲区的数据,再退出!

使用格式:

        #include <stdlib.h>

        void exit(int status);

       status: 退出的状态

               0  -> 正常退出

              非0 -> 异常退出

       返回值:无。

    _exit()  -> 查询: man 2 _exit

    _Exit()  -> 查询: man 2 _Exit

_exit()_Exit是一样的,特点就是不清洗缓冲区数据,直接退出!

       #include <unistd.h>

       void _exit(int status);

       #include <stdlib.h>

       void _Exit(int status);

       status: 退出的状态

               0  -> 正常退出

              非0 -> 异常退出

       返回值:无。

1. 缓冲区问题。

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h>

int main()

{

       printf("hello");

       exit(0); //输出hello,再退出!

       _exit(0); //不输出hello,直接退出!

      

       printf("world"); //无论什么退出函数,都不会运行到这里。

       return 0;

}

2. 进程退出状态。   -> 只要程序中调用exit()/_exit(),都一定会从运行态变成僵尸态。

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h>

#include <sys/wait.h>

int main()

{

       pid_t x;

       int state;

       x = fork();

      

       if(x > 0)

       {

              wait(&state);

              printf("state = %d ",state);

       }

      

       if(x == 0)

       {

              sleep(5);

              exit(0); //这个exit(0)只是说明子进程退出。

       }

       return 0;

}

3. exit()return区别?

举例子。

void fun()

{

       //return;  -> 效果:就会打印hello

       exit(0);   -> 效果:不会打印hello

}

int main()

{

       fun();

       printf("hello! ");

       return 0;

}

结论:

return  -> 只是代表函数的结束,返回到函数调用的地方。
exit()  -> 代表整个进程的结束,无论当前执行到哪一行代码,只要遇到exit(),这个进程就会马上结束!

. 从内存角度分析父子进程资源问题。

举例子。

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h>

#include <sys/wait.h>

 

int main(int argc,char *argv[])

{

       int a = 100;

       pid_t x;

       x = fork();

      

       if(x > 0)

       {

              //int a = 100;

              sleep(1);

              printf("parent a = %d ",a); //100

       }

      

       if(x == 0)

       {

              a = 50;

              printf("child a = %d ",a); //50

       }

      

       return 0;

}

 

结论:

1fork()之后,父进程会复制一份几乎与父进程一模一样的资源给子进程(PID号除外)

2)父子进程拥有独立的空间,在其中一个进程中修改数据,不会影响到另外一个进程的数据。

. exec函数族接口。 -> man 3 execl

    #include <unistd.h>

  int execl(const char *path, const char *arg, ...);

   path: 需要执行的那个程序的名字,例如: /home/gec/project

   arg: 需要运行时传递的参数,例如: "project","aaa",NULL

  返回值:

       成功: 非-1

       失败: -1

  例子: 产生一个子进程,让子进程执行"ls -l"这个程序。

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h>

#include <sys/wait.h>

 

int main(int argc,char *argv[])

{

       pid_t x;

      

       x = fork();

       if(x > 0)

       {

              sleep(2);

              printf("I am parent! ");

       }

      

       if(x == 0)

       {

              printf("apple tree! ");

              execl("/bin/ls","ls","-l",NULL);

              printf("helloworld! ");  -> exec函数族替换掉一个程序,子进程之后的代码都不会执行。

       }

      

      

       return 0;

}

 

补充:在GEC6818平台播放mp3歌曲命令

[root@GEC6818 /]#madplay jay.mp3

MPEG Audio Decoder 0.15.2 (beta) - Copyright (C) 2000-2004 Robert Leslie et al.

          Title: ····

         Artist: ···

          Album: ········

原文地址:https://www.cnblogs.com/zjlbk/p/11322760.html