p { margin-bottom: 0.25cm; line-height: 120% }
(一) 认识进程
在Linux系统中,每一个进程都有自己的ID,就如同人的身份证一样。linux中有一个数据类pid_t。 该数据用来定义进程的ID。其实就是一个非负的整数
进程的状态:运行态,等待态,结束态,就绪,挂起和僵尸状态。进程就是在这几个状态间来回切换。
首先来看下如何创建新的进程,这里需要用到fork函数。使用fork函数需要用到<sys/types.h>和<unistd.h>头文件,函数的返回类型为pid_t。表示一个非负的整数。代码如下
void get_pid()
{
pid_t pid;
if ((pid=fork())<0)
{
printf("fork error\n");
exit(1);
}
else if(pid == 0)
{
printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the child process!\n");
}
else
{
printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the parent process!\n");
}
exit(0);
}
p { margin-bottom: 0.25cm; line-height: 120% }
执行结果:
the pid is 3738in the parent process!
the pid is 0in the child process!
从结果可以看到返回了两条打印,分别是父进程和子进程。原因在于调用fork函数后,就会出现分叉。在子进程中fork函数返回0, 在父进程中,fork函数返回子进程的ID。也就是fork函数会复制一个进程给子进程。为什么pid的值在父子进程中返回的值不一样呢。原因在于父子进程相当与链表,进程形成了链表,父亲进程的pid意味这指向子进程的id,但是子进程没有id,所以其pid=0
这样fork函数就是调用一次,返回两次。在开发过程中,可以根据返回值的不同,对父进程和子进程执行不同的代码。我们来把代码变动下来看下父进程和子进程的关系。增加了一个全局变量gvar和局部变量var。
int gvar=2;
void get_pid()
{
pid_t pid;
int var=5;
if ((pid=fork())<0)
{
printf("fork error\n");
exit(1);
}
else if(pid == 0)
{
gvar--;
var++;
printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the child process!\n");
}
else
{
printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the parent process!\n");
}
exit(0);
}
p { margin-bottom: 0.25cm; line-height: 120% }
运行结果,在子进程中,对gvar和var分别进行减一和加一的操作。但是在父进程中,gvar和var仍然是原值且并没有被改动。原因在于fork函数胡复制父进程的所有资源,包括内存等。因此父子进程属于不同的内存空间。当修改的时候自然没有同步。
the pid is 4694
, gvar=2,var=5
in the parent process!
the pid is 0
, gvar=1,var=6
in the child process!
除了fork外,还有一个vfork函数,和fork一样都是系统调用函数。两者的区别在于vfork在创建子进程的时候不会复制父进程的所有资源,父子进程共享地址空间。因此子进程中修改的所有变量在父进程也会被修改,因为同属一个地址空间。
int get_pid__vfork()
{
pid_t pid;
int var =5;
printf("process id:%d\n",getpid());
printf("gvar=%d var=%d\n",gvar,var);
if((pid=vfork())<0)
{
printf("error");
return 1;
}
else if(pid == 0)
{
gvar--;
var++;
printf("The child process id:%d\n gvar=%d var=%d\n",getpid(),gvar,var);
exit(0);
}
else
{
printf("the pareent process id:%d\n gvar=%d var=%d\n",getpid(),gvar,var);
return 0;
}
}
p { margin-bottom: 0.25cm; line-height: 120% }
执行结果:
process id:4821
gvar=2 var=5
The child process id:4822
gvar=1 var=6
the pareent process id:4821
gvar=1 var=6
在fork和vfork中,子进程和父进程都是运行同样的代码。那么如果想让子进程执行不同的操作。就需要用到execv函数
在test1.c中的代码:
p { margin-bottom: 0.25cm; line-height: 120% }
void main(int argc,char* argv[])
{
execve("test2",argv,environ);
}
test2.c中的代码
int main()
{
puts("welcome!");
return 0;
}
p { margin-bottom: 0.25cm; line-height: 120% }
首先编译这2个文件,得到两个可执行的文件test1和test2。然后运行./test1. 得到的结果如下:test1.c中执行了test2.c的代码。
root@zhf-linux:/home/zhf/zhf/c_prj# ./test1
welcome!
(二)进程等待:
进程等待就是为了同步父进程和子进程。等待调用wait函数,wait函数的工作原理是首先判断子进程是否存在,如果创建失败,子进程不存在,则会直接退出进程。并且提示相关错误信息。如果创建成功,wait函数会将父进程挂起,直到子进程结束,并且返回结束时的状态和最后结束的子进程PID。来看下具体的代码实现。
oid exit_s(int status)
{
if(WIFEXITED(status)){
printf("normal exist,status=%d\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)){
printf("signal exit!status=%d\n",WTERMSIG(status));
}
}
void wait_function_test()
{
pid_t pid,pid1;
int status;
int ret;
int pr;
if((pid=fork())<0)
{
printf("child process error!\n");
exit(0);
}
else if(pid == 0)
{
printf("the pid of child is %d\n",getpid());
printf("The child process\n");
sleep(3);
exit(2);
}
else
{
printf("the pid of parent is %d\n",getpid());
printf("I am waiting for child process to exit\n");
pr=wait(&status);
if (pr > 0){
printf("I catched a child process with pid of %d\n",pr);
}
exit_s(status);
}
}
p { margin-bottom: 0.25cm; line-height: 120% }
在子进程中调用sleep(3)睡眠3秒钟。只有子进程从睡眠中苏醒过来,才能正常退出并被父进程捕捉到。在此期间父进程会继续等待下去。在wait函数中会将子进程的状态保存在status中
在 exit_s中调用了几个宏:
WIFEXITED:当子进程正常退出时,返回真值
WEXITSTATUS:返回子进程正常退出时的状态。只有当WIFEXITED为真值的时候。
WTERMSIG:用于子进程被信号终止的情况。返回此信号类型
执行结果如下,可以看到在父进程等到3秒后,子进程开始退出。然后被父进程捕获到。
the pid of parent is 5063
I am waiting for child process to exit
the pid of child is 5064
The child process
I catched a child process with pid of 5064
normal exist,status=2
那如果子进程是异常退出的,会是什么样的情况呢。
void wait_function_test_for_childerror()
{
pid_t pid,pid1;
int status;
int ret;
int pr;
if((pid=fork())<0)
{
printf("child process error!\n");
exit(0);
}
else if(pid == 0)
{
pid1=getpid();
printf("the pid of child is %d\n",pid1);
printf("The child process\n");
kill(pid1,9);
}
else
{
printf("the pid of parent is %d\n",getpid());
printf("I am waiting for child process to exit\n");
pr=wait(&status);
printf("the pid is %d\n",pid);
printf("the pr value is %d\n",pr);
if (pr != pid)
{
printf("parent process wait error\n");
}
exit_s(status);
}
}
p { margin-bottom: 0.25cm; line-height: 120% }
在子进程中用kill(pid,9)的方法来杀死进程。其中kill的用法参考kill -l命令。结果如下。
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
执行结果如下:可以看到不再是normal exit而是signal exit,并且状态码也从2变成了9
I am waiting for child process to exit
the pid of child is 5218
The child process
I catched a child process with pid of 5218
signal exit!status=9
(三)线程
首先来看下线程和进程的区别。
进程是资源分配的最小单位,每个进程都是有独立的内存空间
线程是程序执行的最小单位。多个线程共享同一个内存空间。
一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。
总的来说就是进程有独立的地址空间,线程没有单独的地址空间,同一进程内的所有线程共享内存空间
那么线程相对于进程的优势是什么呢:
1
线程不需要额外的内存申请
2
线程共享进程内的数据,访问数据方便。而进程则需要通过通信的方式进行
进程使用ps的方式查看,线程则可以通过top的方式进行查看。还可以通过top
-p pid的方式进行某个进程内的线程查看。
p { margin-bottom: 0.25cm; line-height: 120% }
来看一个创建线程的例子:
void *mythread1(void)
{
int i;
for(i=0;i<5;i++)
{
printf("This is the first thread\n");
sleep(1);
}
}
void *mythread2(void)
{
int i;
for(i=0;i<5;i++)
{
printf("This is the second thread\n");
sleep(1);
}
}
void main(int argc,char* argv[])
{
int ret=0;
pthread_t id1,id2;
pthread_create(&id1,NULL,(void *)mythread1,NULL);
pthread_create(&id2,NULL,(void *)mythread2,NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
}
p { margin-bottom: 0.25cm; line-height: 120% }
mythread1和mythread2两个函数分别属于不同的线程。pthread_t 是线程的id.。pthread_create是创建线程的函数。其中的第一个为线程id。第二个参数指线程的属性,这里设置位空。第三个参数:线程运行函数的起始地址。第四个参数:运行函数的参数。这里也设置位空。
但是在用gcc调试的时候出现如下错误,提示找不到线程的相关函数。原因在于pthread的库不是Linux系统的库,所以在进行编译的时候要加上-lpthread,否则编译不过
root@zhf-linux:/home/zhf/zhf/c_prj#
gcc -g -o test1 test1.c
/tmp/ccH40hx1.o:
In function `main':
/home/zhf/zhf/c_prj/test1.c:188:
undefined reference to `pthread_create'
/home/zhf/zhf/c_prj/test1.c:189:
undefined reference to `pthread_create'
/home/zhf/zhf/c_prj/test1.c:190:
undefined reference to `pthread_join'
/home/zhf/zhf/c_prj/test1.c:191:
undefined reference to `pthread_join'
换成如下就可以正常编译了:
root@zhf-linux:/home/zhf/zhf/c_prj#
gcc -o test1 test1.c -lpthread
root@zhf-linux:/home/zhf/zhf/c_prj#
./test1
执行结果如下:两个线程在交替的运行
This
is the first thread
This
is the second thread
This
is the second thread
This
is the first thread
This
is the first thread
This
is the second thread
This
is the first thread
This
is the second thread
This
is the first thread
This
is the second thread
线程还有很多其他的操作,比如变量互斥,锁,同步,信号等,讲在后面的章节中详细介绍。