说些废话:话说从留校以来,linux C已经看了大半,然而自己还没有系统的总结许多的知识点。今天想来真乃是一大“罪状”啊!!!哈哈哈~~于是乎决定,利用这几天的时间对学过的小知识点系统性的总结一下。PS:仅作为个人的参考资料吧!如果对你有帮助,那真的是瞎猫碰上死耗子了^-_-^
进程:一个程序被加载到内存当中运行,那么在内存当中的那个数据就是进程。
进程与程序的区别:进程是运行的程序,程序只是存放在硬盘上的可执行代码。
1.获取进程的各种ID (非负数)
书上说是函数声明在unistd.h 头文件中,但我man 之后发现是在sys/types.h 和 unistd.h之中,说明尽信书不如无书啊!!!!
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
uid_t getuid(void); //获得进程的实际用户ID
uid_t geteuid(void); /获得进程的有效用户ID
gid_t getgid(void); //获得进程的实际组ID
gid_t getegid(void); //获得进程的有效组ID
实际用户:运行该程序的用户
有效用户:程序以什么用户身份来执行
小例子:我sudo 执行了一个程序,该进程的实际用户是我,而有效用户是root
2.创建进程 (fork)
#include <unistd.h>
pid_t fork(void);
说明: 1.调用成功时两个返回值,父进程返回子进程ID ,子进程返回 0 。失败返回 -1。这样就可以来区别父子进程!
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(void)
{
pid_t pid;
int i;
pid=fork();
switch(pid)
{
case 0:
for(i= 0 ;i< 50 ;i++)
printf("*****************************子进程\n");
break;
case -1:
printf("creat P id failed \n");
break;
default:
for(i= 0 ;i< 50 ;i++)
printf("+++++++++++++++++++++++++++++++++++++++父进程哦!!\n");
break ;
}
return 0;
}
执行结果:
说明:1.创建子进程后,父子进程争夺CPU ,先抢到者先执行,另一个挂起等待 ,所以父子进程的执行取决于你的操作系统。可能你的执行结果就与我的不一样哦!!
2.fork 就相当于两条支流,一条流向0 (子进程),一条流向大整数(父进程)。
3. 子进程的继承性是“拷贝” ,不是拿来就用。
3.孤儿进程的创建
孤儿进程:在其父进程执行完成或被终止后仍继续运行的一类进程。(会被init 或者是systemd进程收养)
4. 守护进程的创建
守护进程(daemon):是一种在后台执行的电脑程序。此类程序会以进程的形式初始化。守护进程的名称通常以字母“d”结尾:例如,syslogd就是指管理系统日志的守护进程。
创建方法:
(1)创建子进程,终止父进程
由于守护进程是脱离控制终端的,因此首先创建子进程,终止父进程,使得程序在shell终端里造成一个已经运行完毕的假象。之后所有的工作都在子进程中完成,而用户在shell终端里则可以执行其他的命令,从而使得程序以僵尸进程形式运行,在形式上做到了与控制终端的脱离。
(2)在子进程中创建新会话
这个步骤是创建守护进程中最重要的一步,在这里使用的是系统函数setsid
setsid函数用于创建一个新的会话,并担任该会话组的组长。调用setsid有三个作用:让进程摆脱原会话的控制、让进程摆脱原进程组的控制和让进程摆脱原控制终端的控制。
在调用fork函数时,子进程全盘拷贝父进程的会话期(session,是一个或多个进程组的集合)、进程组、控制终端等,虽然父进程退出了,但原先的会话期、进程组、控制终端等并没有改变,因此,那还不是真正意义上使两者独立开来。setsid函数能够使进程完全独立出来,从而脱离所有其他进程的控制。
(4)在子进程中创建孙子进程 。
因为setsid() 之后 ,默认会是进程组组长,而只有进程组组长才能申请一个控制终端 。那么可以让它fork后自己再退出,子进程做剩下的事。这个问题就迎刃而解了!!
(5)关闭那些从父进程继承而来的东西 。
关闭文件描述符,更改目录,设置文件屏蔽字,处理SIGCHLD ,信号 等等。
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<time.h>
#include<syslog.h>
#include<unistd.h>
#include<sys/param.h>
#include<signal.h>
int init_daemon(void)
{
int pid;
int i;
signal(SIGTTOU,SIG_IGN); //忽略终端信号
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP,SIG_IGN);
pid=fork() ; //第一次fork
if(pid > 0)
exit(0); //父进程退出
else if(pid < 0)
return -1;
/*申请一个控制终端只能是一个进程组组长,那么可以让它fork后自己再退出,子进程做剩下的事,所以执行过这些步骤后,
下一步还应该调用一次fork(),父进程退出,
子进程关闭继承于父进程打开的文件,
修改自己的工作目录,
然后正式成为一个daemon进程。*/
pid=fork() ; //第二次fork
if(pid > 0)
exit(0);
else if(pid < 0)
return -1;
for(i= 0;i< NOFILE ;close(i++));//关闭文件描述符
chdir("/");//更改为根目录
umask(0);//设置文件屏蔽字为0
signal(SIGCHLD,SIG_IGN);//忽略SIGCHLD信号
return 0;
}
int main(void)
{
init_daemon();
return 0;
}
(6)当进程是会话的进程组长时setsid()调用失败并返回(-1)。setsid()调用成功后,返回新的会话的ID,调用setsid函数的进程成为新的会话的领头进程,并与其父进程的会话组和进程组脱离。由于会话对控制终端的独占性,进程同时与控制终端脱离。
5.进程的退出
正常退出:
- 从main函数返回
- 调用exit
- 调用_exit
异常退出:
- 调用abort
- 由信号终止
几种函数的比较:
- exit 把控制权交给系统 。return 把控制权交给调用函数
- exit 在stdlib.h 头文件中 。 _exit 在unistd.h头文件中
注:exit()就是退出,传入的参数是程序退出时的状态码,0表示正常退出,其他表示非正常退出,一般都用-1或者1,标准C里有EXIT_SUCCESS和EXIT_FAILURE两个宏,用exit(EXIT_SUCCESS);
_exit()函数的作用最为简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit() 函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序。
exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是”清理I/O缓冲”。
两种情况:
1.子进程先于父进程退出。
- 如果父进程没有调用wait 函数来等待子进程结束,子进程进入僵死
- 具体说明:
- 线程退出时会发生的那些事:unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息直到父进程通过wait / waitpid来取时才释放
- 僵尸进程的那些事: 如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
2.父进程先于子进程退出
子进程就会被init 进程收养,成为孤儿进程。
执行新程序
新程序的雏形:子进程调用exec 函数来执行另一个程序,子进程“死亡”。只保留进程ID,执行另一个程序
main 函数原型:main(int argc,char *argv[] ,char *envp[])
exec函数族包括6个函数:
include 《 unistd.h 》
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
int execle(const char *path, const char *arg, const char *envp[]);
int execv(const char *path, const char *argv[]);
int execve(const char *path, const char *argv[], const char *envp[];
int execvp(const char *file, const char *argv[]);
说明:
(以 p 结尾的两个函数)可以只给出文件名,系统就会自动按照环境变量“$PATH” 所指定的路径进行查找,其他函数的查找方式都是完整路径
参数传递方式:exec函数族的参数(argv)传递有两种:一种是逐个列举的方式,而另一种则是将所有参数整体构造指针数组传递。然而,重点是这些参数必须以NULL结束
共同点:无论哪一个exec函数都是将可执行程序的路径,命令行参数,环境变量传递给可执行程序的main函数 。
返回值
调用成功 的 时候 不会 返回, 调用失败 时 返回 -1, 并 设置 errno 为 相应的值.
exec 很容易执行失败,其中最常见的原因有:
① 找不到文件或路径,此时 errno 被设置为 ENOENT。
② 数组argv 和envp 忘记用NULL结束,此时,errno被设置为 EFAUL。
进程等待
#include sys/types.h
#include sys/wait.h
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
说明:
1 .wait 使得父进程暂停,等待一个子进程的结束。返回子进程的PID 。
2.参数:status ,所指向的变量存放子进程的退出码
3.waitpid 中pid 的含义:
1.pid > 0 ,等待进程ID 等于pid 的子进程退出
2.pid == - 1,等待任意 子进程
3.waitpid 与wait 的小比较:wait等待第一个终止的子进程,waitpid 指定等待的子进程 。
程序1:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc,char *argv[] ,char *environ[])
{
int i;
int t ;
t= atoi(argv[1]);
for(i= 0 ;i< t ;i++)
printf("-----------------l55555555555555555555555555555555555\n") ;
return 0;
}
程序2:
#include<sys/wait.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc,char **argv ,char **environ)
{
pid_t pid ;
int stat_val ;
char *a= "50" ;
char *b= "10" ;
char *str[3] = { a,b, NULL};
int n;
pid=fork() ;
switch(pid)
{
case 0: execve("./test",str,environ) ; exit(0);
case -1:printf("ERROR !!\n"); exit(-1);
default : printf("11111111111111111111\n"); break;
}
wait(&n);
printf("----------------%d\n",n);
exit(0);
}
编译语句:gcc -o test execve_1.c ;gcc execve_2.c
;./a.out
执行结果:
说明:1.execve(“./test”,str,environ) ; str 就是传过去的参数,就是被调用函数的argv 数组,str[0] 就是argv[0] ,str[1]就是argv[1]。
2. 参数./test ,最好写为完整路径 。才不会容易出错!!
3. 参数environ ,系统预定义的全局变量,显示各个环境变量的值。
设置ID:
setuid :设置有效用户ID与实际用户ID
stegid:设置有效用户组ID与实际用户组ID
#include <sys/types.h>
#include <unistd.h>
int setgid(gid_t gid);
int setuid(uid_t uid);
说明:内核检查一个进程是否具有访问某文件的权限时,通过有效用户ID 来检查 ,而不是实际用户ID 。
改变进程的优先级
#include <sys/time.h>
#include <sys/resource.h>
int getpriority(int which, int who);//取得程序进程执行优先权)
int setpriority(int which, int who, int prio);//设置进程、进程组和用户的进程执行优先权。
说明:(1):getpriority(int which, int who)
参数which有三种数值,参数who则依which值有不同定义:
which who 代表的意义
PRIO_PROCESS who 为进程识别码
PRIO_PGRP who 为进程的组识别码
PRIO_USER who 为用户识别码
此函数返回的数值介于-20至20 之间,代表进程执行优先权,数值
越低代表有较高的优先次序,执行会较频繁。
返回值:返回进程执行优先权,如有错误发生返回值则为-1且错误原因存于errno。
(2):setpriority(int which, int who, int prio)
参数prio介于-20至20之间。代表进程执行优先权,此优先权默认是0,而只有超级用户(root)允许降低此值。执行成功则返回0,如果有错误发生返回值则为-1,错误原因存于errno。
nice 函数:nice函数改变进程优先级,也就是改变进程执行的优先顺序。
#include <unistd.h>
int nice(int inc);
小示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/time.h>
#include<sys/resource.h>
int main(void)
{
pid_t pid ;
int n ;
int stat_val = 0 ;
int old ,new ;
pid=fork();
switch(pid)
{
case 0:
printf("I m child process ,PID = %d ,PPID = %d \n",getpid() ,getppid() );
old =getpriority(PRIO_PROCESS,0);
printf("old priority is %d \n",old);
new =nice(2);
printf("new priority is %d \n",new);
exit(0) ;
case -1:printf("ERROR!!!\n");exit(-1);
default :printf("I m parents process ,PID = %d ,PPID = %d \n",getpid() ,getppid() );
break ;
}
wait(&n);
exit(0);
}