进程:正在进行/运行的程序 动态
程序:它是存放在硬盘上的可执行文件 静态
任务:进程工作的目的就是完成任务,一般我们把进程和任务等价
当你在终端输入./a.out的时候,终端首先开辟了一片内存,将a.out文件内容拷贝到内存中,然后执行内存的程序,形成进程
如何查看进程/任务:ps -aux, process stat -a: 所有进程
杀死/结束进程:kill -9 +pid(进程号)
pid:process id 每个进程都要有一个进程号
%CPU和%MEM:CPU和内存消耗
COMMAND:进程的名字
一、进程
执行新的任务,一个程序,需要创建多个进程,竞争更多的CPU资源
1.fork()创建子进程
函数原型:int fork(void);
返回值:
-1 表示失败, 克隆失败 ,当前只有一个进程
0,表示 当前进程是一个 子进程
>0: 表示 父进程, 返回值是 孩子的 pid号
实现代码:
#include <stdio.h>
#include <unistd.h>
int main()
{
int aa = 1;//当前的主进程
printf("mypid = %d\n",getpid() );
int subpid = fork(); //之后会生成一个一模一样的进程
if(subpid ==0){
while(1){
printf("I am a child,pid=%d\n",getpid());
aa++;
sleep(1);
}
}
else if(subpid >0){
while(1){
printf("I am father, pid=%d subpid=%d aa=%d\n",getpid(), subpid ,aa);
sleep(1);
}
}
}
2.进程的终结
(1)return 退出main, 本质上其实调用exit( )
#include <stdio.h>
#include <stdlib.h>
int main()
{
for(int i=0;i<100;i++){
if(i==5){
exit(6);
}
printf("main i=%d\n",i);
}
return 0;
}
(2)进程调用exit主动结束自己
函数原型:
#include <stdlib.h>
void exit(int status);
exit会释放掉进程绝大部分资源,但是留有尸体在系统中,尸体中保存了退出码status等待父亲的收尸。父亲收尸彻底清理掉所有资源,获取退出码
status :
0 ,正常退出
其他的码,表示不同的原因
(3)_exit(int status)
和exit区别: eixt函数其实内部调用_exit实现进程推出的,但是exit在调用_exit结束进程之前,会做一些清理工作,主要是清理文件缓存到硬盘。比_exit安全。
总结:
其实上面三种在方法,最终都是调用_exit函数,保留尸体在系统中。exit函数比较安全版本,建议使用
(4)wait函数
父进程调用 wait函数来收尸。如果父进程先死,子进程会托付给 爷爷进程,最终可以托付给init进程。
函数原型:
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *status);
阻塞的等待任一子进程退出,一旦发现子进程退出该函数返回,返回值就是那个退出的子进程的pid ,同时给子进程收尸,彻底释放子进程所有资源。 如果子进程先退出,立马获取返回, status保存了子进程的退出码,保存在了 8-15位
实现代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
printf("I am main pid=%d\n",getpid() );
int subpid = fork();
if(subpid <0){
printf("I am main,fork failed\n");
return -34;
}
/*以下代码 两个进程在执行*/
if(subpid >0){
/* main process code */
int status;
int spid = wait(&status);
if(spid <0){
perror("wwait err");
exit(0); //结束主进程 自己.
}
printf("subpid=%d terminated err code =%d\n",spid, (status >>8)&0xFF );
sleep(1);
exit(0);
}else {
/* sub process code */
printf("I am child pid=%d ppid=%d\n",getpid(),getppid());
sleep(1);
exit(8); //结束自己,残留尸体在系统中. 传递值 给父进程
}
}
3.exec族
读取文件中的内容覆盖子进程本身,然后执行子进程
exec函数族
fork()函数用于创建一个子进程,该子进程几乎复制了父进程的全部内容。我们能否让子进程执行一个新的程序呢?exec函数族就提供了一个在进程中执行另一个程序的方法。它可以根据指定的文件名或目录名找到可执行文件,并用他来取代当前进程的数据段、代码段和堆栈段。在执行完后,当前进程除了进程号外,其他内容都被替换。
l(list) : 表示参数采用列表;
v(vector) : 参数用数组;
p(path) : 有p自动搜索环境变量PATH;
e(env) : 表示自己维护环境变量;
(1)#include <unistd.h>
int execl(const char *path, const char *arg0,char *arg1 ,... , (char *) NULL );
l:list 参数以list列表的形式展示
path:程序本身 sub.out
arg0,arg1 ......表示给程序的传参,分别给子程序 argv[0] argv[1] ...
(2)int execv(const char *path, char *const argv[]);
v:vector矢量 数组, 参数以数组的形式展示
返回值:
success: 永远不会返回了,因为子进程全都被替换了,包括exec函数
-1: errno
execl 和execv的区别只不过是execv把execl以列表的形式传参变成了以数组形式的传参
实现代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
printf("I am main process pid=%d\n",getpid() );
int subpid = fork();
if(subpid == 0){
char *argv[] = {"-s","-l","-t",NULL }; //最后一个一定要为NULL,表示结束
//int ret = execl( "./sub.out","-s","-l","-t",NULL );//最后一个参数必须为NULL
int ret = execv("./sub.out", argv );
if(ret<0){
perror("execl eerr");
exit(9);
}
}else if(subpid > 0 ){
while(1){
printf("I am main process\n");
sleep(1);
}
}
}
Sub.c:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv)
{
for(int i=0; i<argc;i++){
printf("subp %dst arg=%s\n",i,argv[i]);
}
while(1){
printf("I am subp,pid=%d ppid=%d\n",getpid(),getppid() );
sleep(2);
}
return 0;
}
运行结果如下图所示:
4.进程间的通信方式
1.管道
它其实就是一个共享的特殊文件,称为管道文件
无名管道 : 也被淘汰,它只能用于父子进程间进行通信
有名管道 : 它是一个特殊的临时文件,用在任意进程间通信 单向的 FIFO 先进先出
(1)创建 pipe文件
函数原型:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname:指定文件路径 /tmp/communicatexxxx
mode : 0666,文件权限
返回值:
0-success
-1:errno
errno:如果是因为 文件已存在 而报错,那么忽略.
EEXIST:表示 文件已存在
(2)打开文件open
(3)不断地 写入信息
(4)close
(5)unlink( )
实现代码:
comm.h:包含一些头文件供发送端和接收端使用
Comm.h:
#ifndef COMM_HHH
#define COMM_HHH
struct msg_struct {
int temp;
long humiduty;
char windspeed;
char wind_direction[16];
char des[128];
};
#endif
发送方:我们在发送方创建一个命名管道文件,然后以只写的方式打开,之后接收端就可以从该命名管道读取信息
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm.h"
int main(void)
{
int ret;
ret = mkfifo("/tmp/communicatexxxx",0666); //创建文件
if(ret < 0 ){
printf("mkfifo errno=%d des:%s\n",errno,strerror(errno));
if(errno != EEXIST ){
return -1;
}
}
int fd = open("/tmp/communicatexxxx",O_WRONLY);
if(fd < 0 ){
perror("open err");
return -3;
}
struct msg_struct msg;
msg.temp=23;
msg.humiduty = 78;
msg.windspeed = 5;
strcpy(msg.wind_direction,"eastsouth");
strcpy(msg.des,"cloudy");
while(1){
msg.temp++;
msg.humiduty-=3;
msg.windspeed++;
ret = write(fd,&msg,sizeof(msg));
if(ret<0){
perror("write err");
return -45;
}
sleep(1);
}
close(fd);
unlink("/tmp/communicatexxxx");
return 0;
}
接收方:创建一个文件以只读的方式打开命名管道文件,实现和发送方的通信
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm.h"
int main(void)
{
int ret;
ret = mkfifo("/tmp/communicatexxxx",0666); //创建文件
if(ret < 0 ){
printf("mkfifo errno=%d des:%s\n",errno,strerror(errno));
if(errno != EEXIST ){
return -5;
}
}
int fd = open("/tmp/communicatexxxx",O_RDONLY);
if(fd < 0 ){
perror("open err");
return -3;
}
struct msg_struct msg;
while(1){
memset(&msg,0,sizeof(msg));
/*如果文件没有数据,则 死等*/
ret = read(fd,&msg,sizeof(msg));
if(ret<0){
perror("read err");
return -45;
}
printf("recv got data:temp%d humidtiy%ld windspeed%d wind_drect=%s\n",\
msg.temp,msg.humiduty,msg.windspeed,msg.wind_direction);
sleep(1);
}
close(fd);
unlink("/tmp/communicatexxxx");
return 0;
}
2.共享内存
(1)创建共享内存
创建/获取 一片共享内存,当指定的magicid的内存不存在的时候,则创建,如果存在 获取。
int shmget( magicID , size, )
magicID:创建内存,并指定该内存的 id.
int shmget(key_t key, size_t size, int shmflg);
keyid: 当指定的magicid的内存不存在的时候,则创建 如果存在 获取
size: 大小
shmflg: 指定共享内存的权限 一般是0666,他还可以 指定本次是创建还是获取。
0666|IPC_CREAT 如果没有,说明本次shmget只会获取, 否则没有则创建
返回值: 是共享内存的描述符,是一个正数,我们以后就使用该id来访问共享内存.
失败: -1 ,errno
(2)将共享内存,绑定到当前进程,进程就可以通过一个地址来访问了
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:共享内存描述符,它代表了这个共享内存
shmaddr: 进程也可以指定一个地址映射到这片共享内存,但是一般为NULL,将会有系统 自动分配一个地址.
shmflg::0666权限
返回值: 返回一个地址,进程使用该地址 访问共享内存的指针
失败返回 (void*)-1 errno
(3)访问
(4)解绑定: 断开进程和共享内存的连接
int shmdt(const void *shmaddr);
shmaddr:代表了那片地址空间
返回值:
0 success
-1:errno
(5)销毁共享内存
它是一个 多功能函数,可以 获取/设置/修改共享内存的信息,同样还可以删除该共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
cmd:
IPC_RMID :表示删除该内存段,如果你要删除 不需要参数buf==NULL
IPC_STAT:获取共享内存信息
IPC_SET::设置修改共享内存信息
返回值:
0-success
-1:errno
实现代码
Comm.h:
#ifndef COMM_H
#define COMM_H
struct msg_struct {
int temp;
int humidity;
char wind;
char des[16];
};
#define SHM_MAGICID 1646466146
#endif
发送方:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "comm.h"
int main()
{
int ret;
int shmid = shmget( SHM_MAGICID ,256, 0666|IPC_CREAT ); //IPC_CREATE 没有就创建
if(shmid <0){
perror("shget create failed");
return -45;
}
/*将共享内存绑定到 进程,进程获取 访问共享内存的指针 */
struct msg_struct *pshm = shmat(shmid,NULL,0666);
if( pshm == ( (void*)-1 ) ){
perror("shmat err");
return -34;
}
pshm->data_flags = 0;
pshm->temp = 35;
pshm->humidity = 58;
pshm->wind = 5;
strcpy(pshm->des,"sunnday");
while(1){
if(pshm->data_flags == 0){ //如果发现没有数据,则写入
pshm->temp++;
pshm->humidity+=2;
pshm->wind+=4;
//写完毕
pshm->data_flags = 1;
}
usleep(1000 * 300); /* ns us ms s */
}
ret = shmdt(pshm);
if(ret<0){
perror("shmdt err");
return -45;
}
/* 销毁*/
ret = shmctl(shmid, IPC_RMID,NULL);
if(ret<0){
perror("shmdel err");
return -45;
}
return 0;
}
接收方:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "comm.h"
int main()
{
int ret;
int shmid = shmget( SHM_MAGICID ,256, 0666|IPC_CREAT ); //IPC_CREATE 没有就创建
if(shmid <0){
perror("shget create failed");
return -45;
}
/*将共享内存绑定到 进程,进程获取 访问共享内存的指针 */
struct msg_struct *pshm = shmat(shmid,NULL,0666);
if( pshm == ( (void*)-1 ) ){
perror("shmat err");
return -34;
}
while(1){
if(pshm->data_flags == 1){ //如果有数据则读出来
printf("reader got:temnp%d huim%d wind=%d des:%s\n", \
pshm->temp,pshm->humidity,pshm->wind,pshm->des);
pshm->data_flags = 0;
}
usleep(1000* 1000);
}
ret = shmdt(pshm);
if(ret<0){
perror("shmdt err");
return -45;
}
/* 销毁*/
ret = shmctl(shmid, IPC_RMID,NULL);
if(ret<0){
perror("shmdel err");
return -45;
}
return 0;
}
3.信号
信号有很多种,每个信号都有一个 int id
SIGHUP:当终端关闭的时候,回想所有的 子进程发送该信号,进程收到该信号,默认 自杀
SIGINT:当你ctrl+c的时候,系统会给该进程发送一个SIGINT,进程收到该信号后自杀
SIGEVG:段错误 当进程访问非法地址,系统会给该进程发送这个信号,进程收到后自杀
SIGKILL:kill -9 pid,,kill是个命令,作用是发送信号 kill -信号编号 进程pid,进程收到信 号后自杀该信号不允许改变处理方式
SIGCHID:当子进程死掉的时候,会主动给父进程发送信号,父进程收到信号后会调用wait 函数来收尸
SIGALRM:定时器信号
SIGTSTP:当你ctrl+z的时候,系统给进程发送该信号
SIGUSR1/SIGUSR2:用户可以自定义的功能
进程收到信号之后,每个信号都有默认的动作: 一般都是自杀
进程也可以自己制定当收到某种信号的时候,自己的处理动作
进程要 指定 收到某种信号之后的动作
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum:表示具体的某个信号
handler:代表 要处理该信号的函数 ,原型 void signal_handlex (int signum)
返回值:
成功 返回handle指针
失败 返回 SIG_ERR (void*)-1
信号的应用:
(1)进程捕获进程执行自己的操作
(2) 定时器信号 SIGALRM 14
原理: 系统每隔一段时间给进程发送 SIGALRM信号,这样进程每隔一段时间,就会执行一次对应的代码
(3)子进程exit会给父进程发送 SIGCHLD ,父进程收到该信号后,再去wait收尸, 就不会卡住
父进程:
void signal_handle(int signum)
{
pid = wait(&exitcode);
其他处理
}
int main()
{
signal(SIGCHLD, signal_handle)
.............
pid = fork();
if(pid==0){ //子进程
exec("sub.out");
}else { 父进程
.....
//wait(); 以前父进程死等收尸
........
}
}
(4)后台服务器进程
当终端关闭,你的子程序会收到 SIGHUP信号,然后自杀
方法一:你的程序 捕获SIGHUP信号,就可以了
方法二:nohup ./yourapp.out &
nohup命令:让你的app忽略SIGHUP信号
& :让程序去后台执行,即不再占用当前终端了
(5) 进程间通知
技巧: 通过共享内存的方式, 接受者进程把自己的pid写入共享内存,发送者进程获取在合适的时机发送信号即可
实现代码:
#include <stdio.h>
#include <signal.h>
/*signum:表示当前发生了那个信号*/
void sigint_handle(int signum)
{
printf("process recv sigINT\n");
}
void sigsegv_handle(int signum)
{
printf("process recv sigsegv\n");
//exit(0);
}
int main()
{
signal(SIGINT,sigint_handle ); //告诉进程,以后收到SIGINT信号,就去执行后面的方法
signal(SIGSEGV, sigsegv_handle);
while(1){
int *pint = (int*)0x12345678;
*pint = 1234;
printf("I am sleeping\n");
sleep(1);
}
return 0;
}
4.网络通信(socket)
二、线程(thread)
一个进程内部其实 也可以创建多个 线程,各个线程独立运行(不需要拷贝fork),
进程内部的线程,共享了进程的资源. (不需要进程间通信)
1.线程的创建
创建一个线程(执行单元, 执行体 应该也是 一个函数)
tid: thread id,每个线程的有一个编号 int
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *thread_funx (void *),void *arg);
pthread_t:线程的标示
attr:线程的属性设置 NULL
thread_funx: 线程执行体所要 执行的函数,类比main
arg:执行上述函数,要传递的参数
返回值:
0-success
其他 返回的是 errno
2.线程的结束
(1)线程退出 循环
(2)线程主动调用pthread_exit退出
上述两种方式,不管哪种方式 都会残留一点尸体在系统中,同时向主线程传递了一个指针
线程的退出:
#include <pthread.h>
void pthread_exit(void *retval);
该函数会释放线程资源,并向主线程main传递消息,最后留下尸体在系统中
等待主线程main 来收尸
(3)主线程给其他线程收尸
#include <pthread.h>
int pthread_join(pthread_t tid, void **retval);
tid:只等待该线程
retval: 获取该线程tid,的返回指针
阻塞等待(死等)#某个#子线程退出,如果发现某个子线程退出,则该函数返回,并得到该线程传递的指针
返回值:
0-success , 否则 errno
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
/*代码编译 gcc thread.c -o thread.out -lpthread*/
struct pthead_private_data {
int exitcode;
int temp;
int humidity;
char wind;
char des[32];
};
int aaaaaa = 1;
int thread_exit_flags = 0; //0-线程不退出 1-线程退出
void *thread_funx_handle (void *arg)
{
int dd;
struct pthead_private_data *p=arg; //全局变量
while(1){
if(thread_exit_flags == 1){
p->exitcode = 8;
pthread_exit(p); //线程主动退出,释放资源,并向主线程传递指针p
}
printf("in thread temp=%d wind=%d des:%s aa=%d\n",p->temp,p->wind,p->des,aaaaaa);
sleep(1);
}
printf("I am thread,i will exit\n");
}
struct pthead_private_data pdata;
int main(int argc,char **argv )
{
int ret;
pdata.temp=1234; pdata.humidity=456;pdata.wind=46;strcpy(pdata.des,"cloudy");
pthread_t tid; /*每个线程 都有一个编号, 称为线程id,简称 thread id :tid */
ret = pthread_create( &tid, NULL, thread_funx_handle, &pdata );
if(ret){
printf("pthread create err %s\n",strerror(ret));
return -34;
}
while(1){
aaaaaa++;
if(aaaaaa==5){
thread_exit_flags = 1;
printf("end thread\n");
struct pthead_private_data *pdata; //存放子线程的返回值指针
ret = pthread_join(tid, (void**)&pdata);
if(ret){
printf("pthread_join err %s\n",strerror(ret));
return -34;
}
printf("main thread find subthread exit code=%d\n",pdata->exitcode);
}
printf("I am main \n");
sleep(1);
}
return 0;
}
(4)Thread_death
主线程结束和子线程的关系 detach ,那么子线程就找不到主线程了,当子线程结束的时候,会自动释放所有资源。
pthread_detach( pthread_t tid);
#include <stdio.h>
#include <pthread.h>
struct private {
int temp;
long data;
char des[128];
};
int sub_thread_run = 1; //子线程可以运行
void *thread_handle_fun(void *args)
{
struct private *p = args;
while( 1 ){
if(sub_thread_run == 0){
printf("I am sub thread,I will exit\n");
pthread_exit(NULL);
}
printf("in sub thread\n");
sleep(1);
}
return NULL;
}
struct private data;
int main(int argc,char **argv)
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,thread_handle_fun,&data);
if(ret){
printf("pthread_create err\n");
return -34;
}
pthread_detach(tid);
int aa = 1;
while(1){
if( (aa++) == 5){
sub_thread_run = 0;// 让子线程结束
}
printf("I am main\n");
sleep(1);
}
return 0;
}