2019年8月8日星期四
一. 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)孤儿进程特点:当自己的父进程退出后,会马上寻找继父,而不是等到变成僵尸再找。
5)init祖先进程特点:一定会帮所有的孤儿回收资源。
五. 进程的函数接口?
单进程程序 -> 只能一行一行代码去执行。
创建子进程意义: 同时处理多个任务。
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)父子进程谁先运行,是随机的。
2)fork()后的代码,两个进程都会执行。
练习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;
}
结论:
1)fork()之后,父进程会复制一份几乎与父进程一模一样的资源给子进程(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: ········